How does mocha / babel transpile my test code on the fly? - node.js

My question is not about why something is not working, but rather why it is. Yes.
I have a small nodeJS command line tool, which contains features that nodeJS does not yet support out of the box, most notably:
import statements
String.includes().
Thus for delivery(build) I transpile+bundle my source code (using parcel, just like webpack).
As a positive wonder, all (but one) of my mocha tests run directly against my classes, not the bundle. Still, they work! Including many import statements. And including an 'ES6 self-test':
it( 'String - include', () => {
var s = 'Southern Bananas'
assert( s.includes( 'anana' ) )
assert( !s.includes( 'kiwi' ) )
} )
Thus:
I have String.include in my test code, not just in the source under test. And there is no place where I transpile or bundle my test codeā€¦ Thus apologies for my dumb question:
Why is this working? Is there a secret just-in-time compilation somewhere? (and if yes, could I use that for a debug flavour of my deliverable code-under-test as well?)
my mocha.opts are rather simple:
--require #babel/register
--require ./test/once.js (nothing special here, either)
--reporter list
--recursive
my .babelrc has this:
{
"presets": [
[
"#babel/preset-env",
{
"targets": {
"Electron": "3.0",
"Node": "8.0"
}
}
]
],
"plugins": [
"#babel/plugin-transform-runtime"
],
"retainLines": true,
"comments": false,
"sourceMaps": true
}
#babel/plugin-transform-runtime is apparently not to blame praise, as it explicitly states
NOTE: Instance methods such as "foobar".includes("foo")
will not work since that would require modification of
existing built-ins (you can use #babel/polyfill for that).
Is #babel/polyfill contained in the minimalistik-modern afaik #babel/preset-env? What else an I doing right :+)? Is there a way to use this live compilation for my (debug) build as well?

Long story short
String.prototype.includes is supported by Node.js since v6.5. #babel/register is causing your code to be compiled on the fly, that's why your import statements work. I doubt you need the #babel/plugin-transform-runtime plugin, unless I'm missing something that you're trying to achieve.
What can cause this confusion?
I think there are two root causes to this (totally understandable) mystery:
The Babel authors have made it really easy to use the tool; and sometimes it is hard to know how/when it is being invoked (especially when paired with another tool like Mocha).
What is/isn't supported natively by Node.js (in terms of ES2015, ES2016, etc.) has traditionally been hard to keep up with.
So, on to the two mysteries.
Why does String.prototype.includes work?
This one has the easier explanation. String.prototype.includes has been supported natively since as early as Node.js v6.5 (as you can see, a vast majority of ES2015 support has been supported since that version).
So, while you're correct that you don't have #babel/polyfill configured (as far as I can tell) and that you would need it in an environment that doesn't support String.prototype.includes, your environment already supports it!
From a Node.js v8.x REPL:
> 'ES2015'.includes('2015')
true
Why does your import statement work?
As you've stated, Node.js v8.x does not natively support ECMAScript Modules. However, there are some good write ups about how it has been enabled as an experimental feature starting in Node.js v9.x.
So, you get the following with native Node.js v8.x (via REPL):
> import path from 'path';
import path from 'path';
^^^^^^
SyntaxError: Unexpected token import
The reason your imports are working is because your code is being compiled by Babel using the #babel/preset-env preset. Furthermore, that compilation is being triggered by your --require #babel/register Mocha option.
#babel/register works by "bind[ing] itself to node's require and automatically compile files on the fly".
Here is a basic example of #babel/register in action:
From the command line:
$ node main.js
You will see this, because there is no syntax error!
main.js
require('#babel/register');
// This next file is compiled on the fly
require('./file1.js');
file1.js
import path from 'path';
console.log('You will see this, because there is no syntax error!');
The good thing is, this is how Mocha recommends you integrate Babel in their documentation. The --require option basically does what the above example does: require('#babel/register'); is called before Mocha uses require to import all of your test files.
Hope this helps! Again, this is a totally understandable mystery in the modern age of rapidly-evolving JavaScript.

Related

Using babel with preset-env on a small node project, unable to convert import/export statements to commonjs

I'm trying to set up a node project so I can use the common import/export syntax, and for compatibility, use babeljs to convert the import and export statements to requires.
When I try transpiling using babel though, I'm not seeing any changes and I don't know why.
Here's my babel.config.js
// babel.config.js
module.exports = {
// I thought this was what I would need to add for making the changes, based on the docs
plugins: ["#babel/plugin-transform-modules-commonjs"],
// env takes care of the majority of changes I might need to convert code to make
// easy to run on a wider range of machines
presets: [
[
"#babel/preset-env",
{
targets: {
// target an older version where I know there is no ESM support
node: "10"
}
}
]
],
// this was in the docs too - I'm not sure if I need it TBH
sourceType: "module"
}
I have a bunch of code using import/export, and async/await in a directory, that I wanted to transpile and put into another directory, lib.
Here's the command I am using:
npx babel src --out-dir lib
My package.json file online too, and there's nothing too esoteric there. I've tried to follow the documentation faithfully.
I would expect to see changes in the code to replace the import/export calls with requires in the lib, but no joy.
What am I doing wrong here? I worked on a related project 6-7 months ago, where I am using babel like this and it does make the changes for me.
You can see the branch with the problem code on github, with the package.json, and here's the source code for other project mentioned where it is working.
Please be gentle, fellow SO commenters - I'm trying my best, I really am.
It looks like you're using "babel-cli": "^6.26.0" instead of "#babel/cli": "^7.11.6" that should fix it.

Using .ts extension in import (like deno) in node

I'm looking for a way to include the .ts extension in typescript import statements then execute it in node using something like ts-node. I'm also looking for a way to have the .ts extension be valid within VSCode and not throw an error.
My goal would be able to run something like this in both ts-node and deno
import alpha from './alpha.ts'
import beta from './beta.ts'
console.log({alpha, beta})
I was hoping the combination of (1) the plugin deno_ls_plugin mentioned here and (2) tsconfig-paths would work, something like:
This tsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"./*": ["./*"],
"../*": ["../*"],
},
"plugins": [{ "name": "deno_ls_plugin" }]
}
}
And run using this to run:
ts-node -r tsconfig-paths/register ./merge.ts
This doesn't work.
In an ideal world the differences between typescript code and deno would be remedied in a single register, something like ts-node -r ts-deno/register ./merge.ts.
I'm essentially looking for a ways of transforming deno-compatible-typescript to node-compatible-javascript. The main issue is that deno-compatible-typescript isn't even compatible with node-compatible-typescript.
The main compatibility issues being:
ts extensions in imports
top-level await
url imports
Deno global (needs shim like YounGoat/nodejs.deno)
Unfortunately, it seems that it's not possible to use ts-node directly for that.
One option would be to switch to webpack, as it will support .ts extension in imports, but it does not perfectly replace ts-node as it's only for transpilation.
Globally, as I understand your goal, I will suggest you to have a look at https://www.edgedb.com/blog/how-we-converted-our-node-js-library-to-deno-using-deno and see if it helps (you'll need some extra work).

Get access to ES6 class in gulpfile.js [duplicate]

This question already has answers here:
Node.js - SyntaxError: Unexpected token import
(16 answers)
Closed 3 years ago.
I'm trying to get the hang of ES6 imports in Node.js and am trying to use the syntax provided in this example:
Cheatsheet Link
I'm looking through the support table, but I was not able to find what version supports the new import statements (I tried looking for the text import/require). I'm currently running Node.js 8.1.2 and also believe that since the cheatsheet is referring to .js files it should work with .js files.
As I run the code (taken from the cheatsheet's first example):
import { square, diag } from 'lib';
I get the error:
SyntaxError: Unexpected token import.
Reference to library I'm trying to import:
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
What am I missing and how can I get node to recognize my import statement?
Node.js has included experimental support for ES6 support.
Read more about here: https://nodejs.org/docs/latest-v13.x/api/esm.html#esm_enabling.
TLDR;
Node.js >= v13
It's very simple in Node.js 13 and above. You need to either:
Save the file with .mjs extension, or
Add { "type": "module" } in the nearest package.json.
You only need to do one of the above to be able to use ECMAScript modules.
Node.js <= v12
If you are using Node.js version 9.6 - 12, save the file with ES6 modules with .mjs extension and run it like:
node --experimental-modules my-app.mjs
You can also use npm package called esm which allows you to use ES6 modules in Node.js. It needs no configuration. With esm you will be able to use export/import in your JavaScript files.
Run the following command on your terminal
yarn add esm
or
npm install esm
After that, you need to require this package when starting your server with node. For example if your node server runs index.js file, you would use the command
node -r esm index.js
You can also add it in your package.json file like this
{
"name": "My-app",
"version": "1.0.0",
"description": "Some Hack",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node -r esm index.js"
},
}
Then run this command from the terminal to start your node server
npm start
Check this link for more details.
I just wanted to use the import and export in JavaScript files.
Everyone says it's not possible. But, as of May 2018, it's possible to use above in plain Node.js, without any modules like Babel, etc.
Here is a simple way to do it.
Create the below files, run, and see the output for yourself.
Also don't forget to see Explanation below.
File myfile.mjs
function myFunc() {
console.log("Hello from myFunc")
}
export default myFunc;
File index.mjs
import myFunc from "./myfile.mjs" // Simply using "./myfile" may not work in all resolvers
myFunc();
Run
node --experimental-modules index.mjs
Output
(node:12020) ExperimentalWarning: The ESM module loader is experimental.
Hello from myFunc
Explanation:
Since it is experimental modules, .js files are named .mjs files
While running you will add --experimental-modules to the node index.mjs
While running with experimental modules in the output you will see: "(node:12020) ExperimentalWarning: The ESM module loader is experimental.
"
I have the current release of Node.js, so if I run node --version, it gives me "v10.3.0", though the LTE/stable/recommended version is 8.11.2 LTS.
Someday in the future, you could use .js instead of .mjs, as the features become stable instead of Experimental.
More on experimental features, see: https://nodejs.org/api/esm.html
Using Node.js v12.2.0, I can import all standard modules like this:
import * as Http from 'http'
import * as Fs from 'fs'
import * as Path from 'path'
import * as Readline from 'readline'
import * as Os from 'os'
Versus what I did before:
const
Http = require('http')
,Fs = require('fs')
,Path = require('path')
,Readline = require('readline')
,Os = require('os')
Any module that is an ECMAScript module can be imported without having to use an .mjs extension as long as it has this field in its package.json file:
"type": "module"
So make sure you put such a package.json file in the same folder as the module you're making.
And to import modules not updated with ECMAScript module support, you can do like this:
// Implement the old require function
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
// Now you can require whatever
const
WebSocket = require('ws')
,Mime = require('mime-types')
,Chokidar = require('chokidar')
And of course, do not forget that this is needed to actually run a script using module imports (not needed after v13.2):
node --experimental-modules my-script-that-use-import.js
And that the parent folder needs this package.json file for that script to not complain about the import syntax:
{
"type": "module"
}
If the module you want to use has not been updated to support being imported using the import syntax then you have no other choice than using require (but with my solution above that is not a problem).
I also want to share this piece of code which implements the missing __filename and __dirname constants in modules:
import {fileURLToPath} from 'url'
import {dirname} from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
If you are using the modules system on the server side, you do not need to use Babel at all. To use modules in Node.js ensure that:
Use a version of node that supports the --experimental-modules flag
Your *.js files must then be renamed to *.mjs
That's it.
However and this is a big however, while your shinny pure ES6 code will run in an environment like Node.js (e.g., 9.5.0) you will still have the craziness of transpilling just to test. Also bear in mind that Ecma has stated that release cycles for JavaScript are going to be faster, with newer features delivered on a more regular basis. Whilst this will be no problems for single environments like Node.js, it's a slightly different proposition for browser environments. What is clear is that testing frameworks have a lot to do in catching up. You will still need to probably transpile for testing frameworks. I'd suggest using Jest.
Also be aware of bundling frameworks. You will be running into problems there.
Use:
"devDependencies": {
"#babel/core": "^7.2.0",
"#babel/preset-env": "^7.2.0",
"#babel/register": "^7.0.0"
}
File .babelrc
{
"presets": ["#babel/preset-env"]
}
Entry point for the Node.js application:
require("#babel/register")({})
// Import the rest of our application.
module.exports = require('./index.js')
See How To Enable ES6 Imports in Node.js
You may try esm.
Here is some introduction: esm
Using the .mjs extension (as suggested in the accepted answer) in order to enable ECMAScript modules works. However, with Node.js v12, you can also enable this feature globally in your package.json file.
The official documentation states:
import statements of .js and extensionless files are treated as ES modules if the nearest parent package.json contains "type": "module".
{
"type": "module",
"main": "./src/index.js"
}
(Of course you still have to provide the flag --experimental-modules when starting your application.)
Back to Jonathan002's original question about
"... what version supports the new ES6 import statements?"
based on the article by Dr. Axel Rauschmayer, there is a plan to have it supported by default (without the experimental command line flag) in Node.js 10.x LTS. According to node.js's release plan as it is on 3/29, 2018, it's likely to become available after Apr 2018, while LTS of it will begin on October 2018.
Solution
https://www.npmjs.com/package/babel-register
// This is to allow ES6 export syntax
// to be properly read and processed by node.js application
require('babel-register')({
presets: [
'env',
],
});
// After that, any line you add below that has typical ES6 export syntax
// will work just fine
const utils = require('../../utils.js');
const availableMixins = require('../../../src/lib/mixins/index.js');
Below is definition of file *mixins/index.js
export { default as FormValidationMixin } from './form-validation'; // eslint-disable-line import/prefer-default-export
That worked just fine inside my Node.js CLI application.
I don't know if this will work for your case, but I am running an Express.js server with this:
nodemon --inspect ./index.js --exec babel-node --presets es2015,stage-2
This gives me the ability to import and use spread operator even though I'm only using Node.js version 8.
You'll need to install babel-cli, babel-preset-es2015, and babel-preset-stage-2 to do what I'm doing.

ts-node programmatic usage with require('ts-node/register')

ts-node suggests to use require('ts-node/register'). And this can be seen in angular2-webpack-starter Protractor configuration.
What is require('ts-node/register') supposed to do? Does it patch require to transpile TS files, so a part of Node.js application could be written in TypeScript?
It does what you think it does. require('ts-node/register') is actually the same as:
require('./').register({
lazy: true
})
See examples here: https://github.com/TypeStrong/ts-node/tree/master/register
The .register function registers the Typescript compiler for files with .ts and .tsx extension for compilation on the fly.

How to get support of generators in typescript without setting target to ES6?

Got a situation here.
I use nodejs with --harmony flag to get support of generators.
Next i'm trying to switch my project to TypeScript and get a problem: in "target":"ES6" mode it transpiles import commands as is (instead of require).
And node with --harmony flag doesn't support that:
import * as fs from 'fs';
^^^^^^
SyntaxError: Unexpected reserved word
Transpiling option "module":"commonjs isn't allowed with "target":"ES6".
Have anyone solved this problem without using any external require/import utilities?
These settings have worked for me:
tsconfig.json
{
"compilerOptions": {
"target":"ES6",
"moduleResolution": "classic",
}
}
ES6 support for generators
No import stuff transpiling due to
"moduleResolution": "classic"
And so the problem's gone!
As you can see in the TypeScript roadmap (Version 1.7) one of the current issues is "Support --module with --target es6".
I'm afraid your are going to need a temporal solution until TypeScript 1.7 is released. Maybe Polyfill for the ES6 Module Loader or SystemJS?
Another way to get all i want is a build stack:
Transpile TS to ES6
Transpile es6-js to es5-js with Babel

Resources