15

Babel's 6th version changes the functioning of export default and in particular its relation with commonjs require.

To summarise, while until babel5, require('module') where giving the default export of the module, it now always returns the module object containing all of the exports of the module. If one only wants the default, he/she must use require('module').default. As explained here, there is very good reasons behind this and the aim of this question is not to break or hack this behaviour.

However, if one is building a library, he/she usually does not want to distribute a module but the export value of his library (e.g. a function, whatever module system is used internally). This is well dealt with by webpack and the output.library configuration when using commonjs or AMD. Because prior babel's versions allowed the default export to be required with commonjs, babel was also compatible with this mechanism. However it is not the case anymore: the library now always provides an es6 module object.

Here is an example.

src/main.js

export default "my lib content";

webpack.config.js

var path = require("path");
var webpack = require("webpack");

module.exports = {
  entry: {
    lib: [ path.resolve(__dirname, "src/main.js") ],
  },
  output: {
    path: path.join(__dirname, "dist"),
    filename: "mylib-build.js",
    library: 'myLib'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: "babel",
        include: path.join(__dirname, "src"),
        query: { presets: ['es2015'] }
      }
    ]
  }
};

test.html

<html>
<head></head>
<body>
<script src="dist/mylib-build.js"></script>
<!-- `myLib` will be attached to `window` -->
<script>
  console.log(JSON.stringify(myLib)); // { default: "my lib content" }
</script>
</body>
</html>

This is a very simple example but I obviously want the export of mylib to be the string "my lib content" instead of { default: "my lib content" }.

One solution could be to create an export source file in commonjs to perform the transformation:

module.exports = require('./main').default;

However I find this solution quite poor. One should be able to solve it at the compilation level, without changing the source code. Any idea?

Community
  • 1
  • 1
Quentin Roy
  • 7,677
  • 2
  • 32
  • 50
  • *"One should be able to solve it has a compilation option and without having to update the source code. Any idea?"* Not quite sure what kind of answer you expect. If you think this should be a compilation option, then you should suggest it to the developer(s). – Felix Kling Nov 12 '15 at 18:44
  • Webpack configuration is pretty powerful. There is many different plugins, and many different parameters for all loaders. – Quentin Roy Nov 12 '15 at 18:49
  • For example, if one only use default export, he could use [babel-plugin-transform-es2015-modules-commonjs](http://babeljs.io/docs/plugins/transform-es2015-modules-commonjs/) and transform all his es6 packages to commonjs. – Quentin Roy Nov 12 '15 at 18:51
  • You are going to do it in a wrong way. Babel changed its behavior to be more compatible with the spec, and you want to break it again. Now you can make an implicit bridge between ES6 and CommonJS. That thing `module.exports = require('./main').default`, cause fewer errors and WTFs, rather than previous sneaky `module.exports = exports.default` – just-boris Nov 21 '15 at 19:06
  • No. I do not want to break anything. I don't want to change the way modules are exported and loaded. I agree with babel's change. But I do not want my library to provide a module but a function. Even if *internally* it makes use of modules, my consumer, that may not use babel (or any module system), should not have to care about how I built it or organised it. – Quentin Roy Nov 24 '15 at 14:05
  • According to: https://babeljs.io/docs/setup/#webpack You should use the `babel-loader`. Plus I would suggest adding preset and the like in the .babelrc file. In this way you can add more features to babel without touching your WebPack config. – Tokimon Nov 26 '15 at 12:54
  • I do use babel-loader (see `module.loaders[0].loader` of the webpack config, the "-loader" part is added automatically). I currently don't use `.babelrc` though but I can. But I think that all its settings can be put as argument of the loader anyway. – Quentin Roy Nov 26 '15 at 13:35
  • @QuentinRoy did you solve this? I am looking for a good way to do it – vvo Dec 08 '15 at 19:03
  • 1
    @vvo Not yet unfortunately... So I had to use the export solution to give webpack a commonjs module instead. – Quentin Roy Dec 09 '15 at 20:17
  • Your solution may not be desirable, but it saved me a lot of pain and trouble. In the future I may just drop UMD altogether. – Jake Z Dec 11 '15 at 04:48
  • @MarkW Why? I think UMD is a must do so that your library can be used by anyone whatever module mechanism they are using. – Quentin Roy Dec 11 '15 at 11:16
  • @QuentinRoy In the future I expect everyone to use ES6 modules, including Node.js. Maybe that's too optimistic. – Jake Z Dec 13 '15 at 22:32
  • Webpack 2 to the rescue! – Quentin Roy Dec 02 '16 at 04:39

3 Answers3

6

Was just going at this my self. Whether one like to call it a workaround or solution, there seem to be a Babel plugin that "solve it".

Using the plugin babel-plugin-add-module-exports as referenced in https://stackoverflow.com/a/34778391/1592572

Example config

var webpackOptions = {
    entry: {
        Lib1: './src/Lib1.js',
        Lib2: './src/Lib2.js'
    },
    output: {
        filename: "Master.[name].js",
        library: ["Master","[name]"],
        libraryTarget: "var"
    },
    module: {
        loaders: [
            {
                loader: 'babel',
                query: {
                    presets: ['es2015'],
                    plugins: ["add-module-exports"]
                }
            }
        ]
    }
};

This yields Master.Lib1 to be lib1 instead of Master.Lib1.default.

Victor Häggqvist
  • 4,484
  • 3
  • 27
  • 35
  • I guess it is a good enough workaround. However, I think it should only be applied on the entry point. Fully restoring the babel's old behaviour seems a bad idea. – Quentin Roy Apr 19 '16 at 02:13
4

Webpack 2 now supports es6 modules which partially solves this issue. Migrating from webpack 1 to webpack 2 is relatively painless. One just needs to remember to disable babel's es6 module to commonjs conversion to make this work:

.babelrc

{
  "presets": [
    ["es2015", {"modules": false}]
  ]
}

However, unfortunately, it does not work properly with export default (but an issue is opened, hopefully a solution will be released eventually).

EDIT

Good news! Webpack 3 supports the output.libraryExport option that can be used to directly expose the default export:

var path = require("path");
var webpack = require("webpack");

module.exports = {
  entry: {
    lib: [ path.resolve(__dirname, "src/main.js") ],
  },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "mylib-build.js",
    library: "myLib",
    // Expose the default export.
    libraryExport: "default"
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: "babel",
        include: path.resolve(__dirname, "src")
      }
    ]
  }
};
Quentin Roy
  • 7,677
  • 2
  • 32
  • 50
-1

You can use this solution (this is more like workaround, but it allow you to keep your sources from change):

There is a loader called callback-loader. It allow you to change your sources in a build time by calling a callback and put a result instead of it. In other words you can turn all you require('module') into a require('module').default automatically in a build time.

Here is your config for it:

var webpackConfig = {
    module: {
        loaders: [
            { test: /\.js$/, exclude: /node_modules/, loader: 'callback' },
            ...
        ]
    },
    ...
    callbackLoader: {
        require: function() {
            return 'require("' + Array.prototype.join.call(arguments, ',') + '").default';
        }
    }
};
Kreozot
  • 1,577
  • 1
  • 16
  • 26