Promoting Babelized ES module code to native Node 14+ - javascript

I have lot of javascript written to be run by nodejs, but which we ran through the Babel loader at runtime, so that we could write ES syntax -- in particular using import rather than require.
We have a layout like:
package.json
node_modules/
...packages...
top/
server.js
fribbity.js
server.js looks like (I've elided the babel import boilerplate) :
import {fribbity} from 'top/fribbity'
const x = fribbity()
console.log(`fribbity = ${x}`)
while fribbity.js might be
export const fribbity = () => 17
I'd like to promote all this code to use native ES modules in Node 14+. I added "type": "module" to package.json. But now I've run into the module resolution rules. By default, Node now expects my import in server.js to be
import {fribbity} from './fribbity.js'
Are there settings I can apply in package.json, or on the node command line, that would enable node to resolve the imports as they were originally written? That is, preserving the deep import path style (import string begins with no slash or dot, and ends without the ".js" extension)? I've tried several false starts.

Related

Why am I getting this UnhandledPromiseRejectionWarning error when running npm build on my serverless project?

I am getting the following error when trying to run npm build on my serverless aws-nodejs-typescript project and do not understand how to fix it. Anyone able to point me in the right direction please?
npm build
(node:44390) UnhandledPromiseRejectionWarning: Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /usr/local/lib/node_modules/npm/node_modules/chalk/source/index.js
require() of ES modules is not supported.
require() of /usr/local/lib/node_modules/npm/node_modules/chalk/source/index.js from /usr/local/lib/node_modules/npm/lib/utils/explain-dep.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /usr/local/lib/node_modules/npm/node_modules/chalk/package.json.
...
Thanks very much!
The latest version of chalk has changed to ES6 import/export syntax instead of CJS (CommonJS with require). So instead of:
const chalk = require('chalk');
You have to do:
import chalk from 'chalk';
You'll (unfortunately) have to change all the other requires into imports as well, then change all your module.exports into export default and all your exports.whatever to export whatever; where whatever is your thing, or as suggested rename your file to index.cjs to force CJS.
Then there are the docs for import and export if you need them.
Then, you need to add type: "module" to package.json. So much for chalk :)
I didn't like that so I used to just use colors intead, but...

How to prevent typescript from transpiling dynamic imports into require()?

I'm building a discord.js Discord bot. Now for some reason, discord.js doesn't work with ESM modules (a totally separate issue), so my bot app uses CommonJS modules. Now I have another project on my system called Lib, which has a lot of utility functions that I plan to use in several different projects so I don't have to rewrite them. This Lib project uses ESM modules. Since I have to import Lib from DiscordBot, I use the dynamic import syntax in typescript. Now, whenever I transpile my DiscordBot project, the dynamic imports get converted into some ugly javascript module code, and that ugly module code ultimately ends up using require(). Since require() can't import ESM modules, my bot ends up crashing.
I tried however to stop my ts compiler, copy the code from my ts file that imports Lib then pasting that code into the corresponding JS file manually (and removing TS-exclusive features like type annotations and interfaces). Then I ran my bot app, and it worked perfectly fine. But I don't want to have to do this every time. So it's tsc's compiling that's the problem. How do I fix this?
So I understand the purpose is:
Develop the code in TypeScript
Run the compiled code in CommonJS package
Import and use an ES Module
Option 1:
If "module" in tsconfig.json is set to "commonjs", currently there's no way to prevent TypeScript from transpiling dynamic import() into require() - except that you hide the code in a string and use eval to execute it. Like this:
async function body (pMap:any){
// do something with module pMap here
}
eval ("import('p-map').then(body)");
No way TypeScript transpiles a string!
Option 2
Set "module" in tsconfig.json to "es2020". By doing this, dynamic import would not be transpiled into require(), and you can use dynamic import to import a CommonJS or ES Module. Or, you can use the const someModule = require("someModule") syntax to import a CommonJS module (would not be transpiled to ES6 import syntax). You cannot use the ES6 import syntax such as import * as someModule from "someModule" or import someModule from "someModule". These syntaxes will emit ES Module syntax imports ("module" is set to "es2020") and cannot be run in CommonJS package.
Below is a bit information:
If "module" is set to "es2020": dynamic import import() is not transpiled.
If "module" is set to `"es2015": there's an error:
TS1323: Dynamic imports are only supported when the '--module' flag is set to 'es2020', 'esnext', 'commonjs', 'amd', 'system', or 'umd'.
If "module" is set to "commonjs": dynamic imports are transpiled.
Quote tsconfig.json reference for "module" field:
If you are wondering about the difference between ES2015 and ES2020,
ES2020 adds support for dynamic imports, and import.meta.
This is currently not possible. There is a very new issue at GitHub (https://github.com/microsoft/TypeScript/issues/43329), but that is not implemented yet. So everything you can do now is to switch from ESM to CommonJS with your Lib project.
Update 2022
The issue has been closed and there is now a new option for "module" called node12. That should fix the problem
The node12 setting others are talking about did not work for me, but these compilerOptions did, using Typescript 4.7.2:
"module": "CommonJS",
"moduleResolution": "Node16",
This saved my backside, I did not have to migrate all import requires to imports to be able to use an ESM npm lib.
Typescript input source:
import Redis = require('redis');
import * as _ from 'lodash';
export async function main() {
const fileType = await import('file-type');
console.log(fileType, _.get, Redis);
}
CommonJS output:
...
const Redis = require("redis");
const _ = __importStar(require("lodash"));
async function main() {
const fileType = await import('file-type');
console.log(fileType, _.get, Redis);
}
exports.main = main;
What compiler/bundler are you using? I am assuming tsc based on context.
I recommend using esbuild to compile and bundle your TS. You can also use it simply to transform it after using tsc. It has an option called "format" that can remove any module-style imports. See https://esbuild.github.io/api/#format.
Here is a simple example of using.
build.js
const esbuild = require("esbuild");
esbuild.build({
allowOverwrite: true,
write: true,
entryPoints: ["my-main-file.ts"],
outfile: "some-file.bundle.js",
format: "cjs", //format option set to cjs makes all imports common-js style
bundle: true,
}).then(() => {
console.log("Done!");
});
You can then add something like this to your package.json
"scripts": {
"build": "node build.js",
...rest of scripts
Here is an additional link about some caveats using esbuild with typescript. None of these should really be a problem for you. https://esbuild.github.io/content-types/#typescript-caveats
This has been fixed with the addition of the node12 option for the module setting. From the docs:
Available in nightly builds, the experimental node12 and nodenext modes integrate with Node’s native ECMAScript Module support. The emitted JavaScript uses either CommonJS or ES2020 output depending on the file extension and the value of the type setting in the nearest package.json. Module resolution also works differently. You can learn more in the handbook.
If you use this setting without a nightly build, however, it currently produces the following error:
error TS4124: Compiler option 'module' of value 'node12' is unstable. Use nightly TypeScript to silence this error. Try updating with 'npm install -D typescript#next'.
I'm using a variant of the already mentioned eval-based hack to overcome this issue.
So for example, parse-domain is distributed as an ESM module, so importing it like this breaks in a CJS-based node app:
import { fromUrl, parseDomain } from 'parse-domain';
const parseDomainFromUrl = (url: string) => {
return parseDomain(fromUrl(url));
}
And this is how I have managed to get it working:
const dynamicImport = new Function('specifier', 'return import(specifier)');
const parseDomainFromUrl = (url: string) => {
return dynamicImport('parse-domain').then((module: any) => {
const { fromUrl, parseDomain } = module;
return parseDomain(fromUrl(url));
})
};
(Note that parseDomainFromUrl became asynchronous in the process, so it would need to be awaited by the caller.)

Node can't find modules without .js extension

I have a file, test.js with these lines of code inside:
import {blabla} from "./bla";
async function dataGenerator() {
..........
}
(async() => {
console.log('1')
await dataGenerator()
console.log('2')
})()
Note: Ignore the import structure. It is just fictive for the question. In my file the imports are auto.
When I'm trying to run from terminal with node test.js it returns error:
Cannot find module 'D:\bla' imported from D:\test.js
I have added into package.json the line: "type": "module". Without this it returns:
Cannot use import statement outside a module
I'm using node v14. How can I run the test.js without adding to all the imports ".js". There are functions in functions in functions and is complicated to add .js extension. Is there any npm to run it?
Node.js by default does not attempt to guess the file extension when using import for ES modules. This is different from CommonJS modules with require.
In the documentation for the ES module loader you can read how files are found on disk.
The heading 'Customizing ESM specifier resolution algorithm' states:
The --experimental-specifier-resolution=[mode] flag can be used to customize the extension resolution algorithm. The default mode is explicit, which requires the full path to a module be provided to the loader. To enable the automatic extension resolution and importing from directories that include an index file use the node mode.

Importing an external module in javascript [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.

Treat self as a node module for npm

I have a javascript project which is released as a node module. For some reasons, I have some source code using relative paths to import other files in this project:
// <this_module_path>/action/foo.js
import execution from './execution';
import types from '../types';
and also using a module name as a root path to import other files in this project:
// <this_module_path>/action/doSomething.js
// Using module name to import
// equals to import './helpers.js' in this case (in the same folder)
import executionHelpers from 'this-module/action/helpers.js';
// equals to import '../types/helpers' in this case
import typeHelpers from 'this-module/types/helpers.js';
How can I have a such file to import other project files using its module name rather than relative paths?
NodeJS uses CommonJS to import javascript moduels. There is no clear timeline for adding ES6 import / export syntax to NodeJS. So you need to transpile your code using Babel to CommonJS module system before you can run it on NodeJS.
How to do this using CommonJS
Create a separate package for your this-module module. The package needs to be created inside node_modules directory of your main module. You can do this using npm init command inside the node_modules directory.
Inside that file, you need to create a Javascript file (conventionally called index.js and make it the main script of that package.
Your package.json should look something like this:
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
}
In the index.js you can export your variables (such as helpers and types) and you can easily import them in your main package.

Categories

Resources