In Babel 5.x, I can write the following code:
app.js
export default function (){}
index.js
require('babel/register');
require('./app')();
Then, I can run node index.js with no errors. However, using Babel 6.x, running the following code
index.es6.js
require('babel-core/register');
require('./app')();
results in an error
require(...) is not a function
I want to know why?
TL;DR
You have to use
const app = require('./app').default;
app();
Explanation
Babel 5 used to have a compatibility hack for export default: if a module contained only one export, and it was a default export, it was assigned to module.exports. So, for example, your module app.js
export default function () {}
would be transpiled to this
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = function () {};
module.exports = exports["default"];
This was done purely for compatibility with require-ing Babel-transpiled modules (like you are doing). It was also inconsistent; if a module contained both named and default exports, it could not be require-d.
In reality, according to the ES6 module spec, a default export is no different than a named export with the name default. It is just syntactic sugar which can be statically resolved at compile time, so this
import something from './app';
is the same as this
import { default as something } from './app';
That being said, it appears that Babel 6 decided to drop the interoperability hack when transpiling modules. Now, your module app.js is transpiled as
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = function () {};
As you see, no more assignment to module.exports. To require this module, you need to do
const app = require('./app').default;
app();
Or, more concisely, and closer to your original code:
require('./app').default();
Just to follow up with the correct answer above.
If you wanna use default export behavior of babel#5, you can try babel-plugin-add-module-exports plugin.
It's working pretty well for me.
If this doesn't work
require('./app').default()
use
require('./app').default
Without the function call at the end.
Related
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.
For example, I have an AMD module in vanilla JS (not touched by tsc):
// Foo.js
define([], function() {
return class Foo {}
})
with accompanying declaration file:
// Foo.d.ts
declare class Foo {
// ...
}
export default Foo
then I have a typescript module:
// app.ts
import Foo from './Foo'
new Foo // Uncaught TypeError: Foo_1.default is not a constructor
When I compile my typescript module to AMD format with tsc and run it in my RequireJS environment, there's an error that default is undefined because typescript compiles to:
define("app", ["require", "exports", "Foo"], function (require, exports, Foo_1) {
"use strict";
console.log(Foo_1["default"]);
});
I don't want it to grab the default property. I want it to treat a default import like it is simply grabbing the returned value of my define module. Every single module in my project is a define() module, or a typescript module compiled to define() (AMD). I'd simply like to get that return object.
Is there a way to do (configure) this?
EDIT:
I know I can import * as Foo from './Foo', but that is sort of hacky, and it generates errors because the declared module doesn't have named exports. I would like a non-error way to do it, if possible.
EDIT: I learned I can just do
// app.ts
import Foo = require('./Foo')
new Foo // no error
to achieve the result. The only problem with that is it is not ES6-compatible. This means that if we tell typescript to leave ES6 import statements as-is, then other tools won't understand typescript-specific import/export statements. I need a way to use only official ES6 module syntax in a reasonable way.
I'm think that my solution might be to just do a post-tsc-compile transform step to convert default import access points.
Try
import * as Foo from './Foo'
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've written an ES6 module that looks something like this:
export default function({makeCurrentVerUrl, verUrl, fileServer, downloadTokenType, appId}) {
...
}
When compiled by webpack, it looks something like this:
webpackJsonp([5,7],[
/* 0 */
/***/ function(module, exports) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = function (_ref) {
var makeCurrentVerUrl = _ref.makeCurrentVerUrl;
var verUrl = _ref.verUrl;
var fileServer = _ref.fileServer;
var downloadTokenType = _ref.downloadTokenType;
var appId = _ref.appId;
...
};
/***/ }
]);
Which is great, but I'm not sure how to run this file and call my default function.
I can include it,
<script src="/path/to/script.js"></script>
Which I believe will run it automatically, but how can I call the functions I've defined in it from the browser? require is not defined in my browser.
You can set output.library in the configuration. From the docs:
output.library
If set, export the bundle as library. output.library is
the name.
Use this, if you are writing a library and want to publish it as
single file.
output.libraryTarget
Which format to export the library:
"var" - Export by setting a variable: var Library = xxx (default)
"this" - Export by setting a property of this: this["Library"] = xxx
"commonjs" - Export by setting a property of exports:
exports["Library"] = xxx
"commonjs2" - Export by setting module.exports: module.exports = xxx
"amd" - Export to AMD (optionally named)
"umd" - Export to AMD, CommonJS2 or as property in root
Default: "var"
Then you will be able to do
myLibrary()
So the easiest way to do this without changing output.library (assuming you don't want to globalize everything) is to just attach your needed functions to the window. e.g.
// entry-point.js
import foo from './scripts/foo.js';
window.foo = foo;
Or if you want to attach a whole bunch of stuff to the window (not just default), you can do something like:
Object.assign(window, require('./scripts/somelib.js'));
You can also take a look at bundle-loader, the built-in webpack feature require.context or dynamic import()s if you want to include some scripts at run-time.
In Babel 5.x, I can write the following code:
app.js
export default function (){}
index.js
require('babel/register');
require('./app')();
Then, I can run node index.js with no errors. However, using Babel 6.x, running the following code
index.es6.js
require('babel-core/register');
require('./app')();
results in an error
require(...) is not a function
I want to know why?
TL;DR
You have to use
const app = require('./app').default;
app();
Explanation
Babel 5 used to have a compatibility hack for export default: if a module contained only one export, and it was a default export, it was assigned to module.exports. So, for example, your module app.js
export default function () {}
would be transpiled to this
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = function () {};
module.exports = exports["default"];
This was done purely for compatibility with require-ing Babel-transpiled modules (like you are doing). It was also inconsistent; if a module contained both named and default exports, it could not be require-d.
In reality, according to the ES6 module spec, a default export is no different than a named export with the name default. It is just syntactic sugar which can be statically resolved at compile time, so this
import something from './app';
is the same as this
import { default as something } from './app';
That being said, it appears that Babel 6 decided to drop the interoperability hack when transpiling modules. Now, your module app.js is transpiled as
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = function () {};
As you see, no more assignment to module.exports. To require this module, you need to do
const app = require('./app').default;
app();
Or, more concisely, and closer to your original code:
require('./app').default();
Just to follow up with the correct answer above.
If you wanna use default export behavior of babel#5, you can try babel-plugin-add-module-exports plugin.
It's working pretty well for me.
If this doesn't work
require('./app').default()
use
require('./app').default
Without the function call at the end.