I am trying to use ES6 style modules with NodeJS v13.13 and I am running into conflicting behavior.
Originally my project was transpiled with node-babel, but then I enabled the builtin ES6 module support by adding "type": "module" to my package.json file.
At this point, import statements such as import * as esprima from 'esprima'; stopped working correctly. Upon examination, rather than the import creating the function esprima.parse, it was creating a function esprima.default.parse.
Of course this was all fixable by using something like:
import * as esprimaLoad from 'esprima';
const esprima = esprimaLoad.default;
However, what behavior is correct per the spec? Is babel right to strip out / collapse the default object, and is there a bug in the current node.js behavior which is still labeled as experimental? All of this ES Module vs CommonJS module stuff gives me a headache.
Update:
I still haven't gotten to the bottom of this, but there is more involved. esprima.js, one of the libraries where this was an issue, appears to have been created using webpack and is a UMD (universal module definition) format. It starts with the following:
(function webpackUniversalModuleDefinition(root, factory) {
/* istanbul ignore next */
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
/* istanbul ignore next */
else if(typeof exports === 'object')
exports["esprima"] = factory();
else
root["esprima"] = factory();
})(this, function() { ...
I think the webpack UMD format is already meant to work with both import statements in the browser and require statements in node.js. However, when using the experimental modules / babel in node.js it seems like the following does not work at all:
import esprima from 'esprima'
and the following works with babel but not the experimental modules:
import * as esprima from 'esprima'
and the following works with both babel and the experimental modules:
import * as esprimaLoad from 'esprima'
const esprima = esprimaLoad.default?esprimaLoad.default:esprimaLoad;
For modules that don't use this webpack UMD trickery, both the babel and the experimental modules seems to work just fine.
If you are exporting default then you don't need to import it in that roundabout fashion.
The simplest version directly imports the default:
import myDefault from '/modules/my-module.js';
It is also possible to use the default syntax with the ones seen above
(namespace imports or named imports). In such cases, the default
import will have to be declared first. For instance:
import myDefault, * as myModule from '/modules/my-module.js';
// myModule used as a namespace
You can check out more from MDN
So in your case
import esprima from 'esprima';
This should be simplest import statement.
Related
I'm trying to import a library into my authored jQuery-plugin and am getting stuck with what i believe it's babel's default importing behaviour.
Whenever i try
import * as autosize from 'autosize';
// or
import autosize from 'autosize';
// or
const autosize = require('autosize');
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
... rest of the fucking owl...
My console gives me an importing error, stating that require is not defined (since it's a browser environment) and going into the script only shows me that the importing statement has been transformed into an _interopRequireDefault call.
How can i get around this? Is it even possible to use external libraries in an jQuery plugin?
Thanks in advance!
I'm struggling to understand how to import CommonJS modules into an ESM syntax. I'm currently trying to work with the library url-metadata. url-metadata exposes a top-level export as a callable (which does not really conform to CommonJS, AFAIK):
const urlMetadata = require('url-metadata')
urlMetadata(URL, ...)
It's not possible to write:
import urlMetadata from 'urlMetadata'
since no default export is defined.
Instead, I have to write:
import * as urlMetadata from 'url-metadata'
Or:
import urlMetadata = require("url-metadata")
I tried to read up on module loading in Node but I'm still somewhat confused as to what is the correct way to do this and why.
import urlMetadata from 'url-metadata';
is syntactic sugar for
import { default as urlMetadata } from 'url-metadata';
Either will work fine.
The value assigned to module.exports in a CommonJS module is the default export.
See the Node.js docs.
If I have a lib, say utils.js which looks like this
exports.foo = function () {
return 'foo';
};
exports.bar = function () {
return 'bar';
};
Which can be used as follows
import {foo} from './libs/utils';
console.log(foo());
Not very spectacular, but I get the feeling that this problem is the origin of the issue described in this post. Anyway I cannot get this to work in combination with SystemJS. I have to change the code to fix it
import utils from './libs/utils';
console.log(utils.foo());
Here is my systemjs-config file:
SystemJS.config({
map: {
'plugin-babel': 'node_modules/systemjs-plugin-babel/plugin-babel.js',
'systemjs-babel-build': 'node_modules/systemjs-plugin-babel/systemjs-babel-browser.js',
},
packages: {
'.': {
defaultJSExtensions: 'js'
}
},
transpiler: 'plugin-babel'
});
So, it seems only the exports object can be loaded and not the named export. Can this somehow be fixed?
UPDATE I get the impression it could be fixed with formats
meta: {
'./libs/utils.js': {
format: 'cjs'
}
}
But so far it gives the same problems
This behavior is not SystemJS specific. SystemJS behaves like this since version 0.20 because this is what ES6 module interoperability is being standardized to.
When, as in your question, you are importing CommonJS modules (exported via module.exports) using ES6 import, you will only get the entire export, and you cannot immediately destructure the exported names.
However, when you are importing modules which are exported via ES6 export, you will be able to destructure the exported names.
So, it's all by design. Guy Bedford wrote about this on his blog and referenced the module standardization that is going on for NodeJS:
... named exports will no longer be permitted when importing a
CommonJS module from an ES module, and is discussed at
https://github.com/nodejs/CTC/pull/60/files#diff-2b572743d67d8a47685ae4bcb9bec651R217.
That is, import { name } from 'cjs.js', where cjs.js is a CommonJS
module will no longer be supported, and instead will require
import cjs from 'cjs.js'; cjs.name.
An interop workaround by using __esModule:
We will continue to support the __esModule flag in interop though,
allowing lifting of named exports for these cases.
So if the cjs.js module was written:
exports.__esModule = true;
exports.name = function () { ... }
then it would be possible to have import { name } from 'cjs.js';, even
though cjs.js is a CommonJS module, although this __esModule will
eventually in the longer term be deprecated as well.
I have a CommonJS module, called inner.js, that defines a function and then exports that function as the entire module:
// inner.js, a legacy CommonJS module
var foo = function() { return 42; };
module.exports = foo;
In Node, I can readily verify this works as-is.
> var inner = require('./inner.js');
> inner() // prints 42
But that’s a legacy module that I’d like to use from a ES6 module, called outer.js:
// outer.js, an ES6 module
import * as inner from "./inner.js";
export function bar() { return inner(); }
I see that rollup-plugin-commonjs is commonly used in these situations but I can’t get it to work when the CommonJS inner.js module exports a function as the whole module. If, after running rollup and dumping the result to loadme.js, I try to run load the ES6 outer module and try to call the function originally defined in the inner CommonJS module, I get an error:
> var outer = require('./loadme.js')
undefined
> outer.bar()
TypeError: inner$2 is not a function
at Object.bar (/.../so-rollup-question/loadme.js:27:25)
I think I’m just failing to load the CommonJS module correctly, in such a way that the module itself functions as a function. I’m not familiar enough with UMD to get anything meaningful out of inspecting the rollup-output.
The rest of this post is about a minimum example.
Here’s my very simple index.js:
// index.js
export {bar} from "./outer.js";
which is read by my rollup config:
// rollup.config.js
import npm from "rollup-plugin-node-resolve";
import commonjs from 'rollup-plugin-commonjs';
export default {
entry : "index.js",
format : "umd",
moduleName : "sphereModule",
plugins : [ npm({jsnext : true}), commonjs() ],
dest : "loadme.js"
};
I have a complete clonable repository demonstrating the problem.
Assigning directly to module.exports is basically equivalent to having a default export. Hence importing the module as follows should work:
import inner from "./inner.js";
I have an ES6 application (with Babel 6.5 and Webpack) and it successfully imports my modules like this:
import $ from 'jquery';
I wanted to install https://github.com/robflaherty/riveted/blob/master/riveted.js (a plugin for Google Analytics), but as you can see, the code doesn't have something like module.exports = ..., it only defines a global variable riveted, but it has an apparently valid package.json pointing to riveted.js.
So doing something like
import riveted from 'riveted'
riveted.init();
throws an error:
_riveted2.default.init is not a function
import riveted from 'riveted'
riveted.init();
import 'riveted'
riveted.init();
throws an error:
riveted is not defined
import * as riveted from 'riveted'
riveted.init();
throws an error:
riveted.init is not a function
How can I access riveted's init() function?
You can use the webpack exports loader:
var riveted = require("exports?riveted!riveted")
See the shiming modules overview for details
Step 1. modify riveted.js
Add some code after line 18.
// Browser global
root = !root && typeof self == 'object' ? self : root; // add this line
root.riveted = factory();
Because the this is undefined when the file is imported by es6, we use self instead.
self is better than window, it works in both main thread and worker.
Step 2. modify your import path
like this:
import './riveted.js';
riveted.init();
The ./ or ../ is required for import js file directly.
Examples:
import `./file.js`
import `../file.js`
import `./a/b/file.js`
import `../a/b/file.js`
import `../../a/b/file.js`
Tested in chrome.