Exporting ES6 Library Modules as Global Scripts with Webpack

March 16, 2016

After authoring a library (Remerge) using ES6 style modules, aside from consumption with npm, I also wanted to release it as a standalone script so that users could use it by simply including it with a simple script tag - well, if they wanted to, at least (appealing to the lowest common denominator here heh).

The Scenario

So I wanted to be able to do something like this:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Remerge Example</title>
</head>
<body>

  <div id="main">
    // your beautiful site goes here
  </div>

  <script src="mainFunction.js"></script>
  <script src="utiltyFunctions.js"></script>

  <script>
    // use library here
    // `mainFunction` is a function exported as a global variable
    // `utiltyFunctions` is an object exported as a global variable, whose properties are utility functions
    var reducer = mainFunction(...)
    var someFunction = utiltyFunctions.someFunction(...)
  </script>

</body>
</html>

Notice that both scripts have a slightly different export pattern. In my code, I wrote mainFunction such that it was a default export:

const mainFunction = () => {
  // main function
}

export default mainFunction

and utlityFunction as named exports, spread across a number of files (tackling the general case here):

// utils.js
export const someFunction1 = () => {
  // some function1
}

// more-utils.js
export const someFunction2 = () => {
  // some function2
}

The Problem

The problem is that, as it is, bundling these ES6 modules will result in objects that look like this:

var mainFunction = {
  _es6module: true,
  default: // the actual mainFunction
}

which is technically usable, but to use it would look like:

var something = mainFunction.default(...)

which is a real PITA for ensuring a consistent API.

The Fix

The fix for this is simple: simply write a file that imports those ES6 modules the ES6 way, but exports them in nodeJS module.exports style. Webpack will then handle them correctly!

For the top-level function export, simply export the function as is:

// webpack/mainFunction.js
import mainFunction from '../src/mainFunction'

module.exports = mainFunction

For the object whose properties should be utility functions, the solution is similarly straightforward - simply create an (in this case anonymous) object whose properties are those functions:

// webpack/utilityFunctions.js
import * as someFunctions1 from '../src/utils'
import * as someFunctions2 from '../src/more-utils'

module.exports = {
  ...someFunctions1,
  ...someFunctions2
}

For my library, I’ve placed these “helper” files in a webpack folder, as their sole purpose is for Webpack to generate the script files. You can place them anywhere it makes sense.

The Webpack configuration for handling this is very simple (as far as build configs go):

var webpack = require('webpack')

module.exports = {

  entry: {
    mainFunction: './webpack/mainFunction.js',
    utilityFunctions: './webpack/utilityFunctions.js'
  },

  output: {
    path: __dirname + '/dist',
    filename: '[name].js',
    libraryTarget: 'var',
    // `library` determines the name of the global variable
    library: '[name]'
  },

  // don't run Babel on your library's dependencies
  module: {
    loaders: [
      { test: /\.js$/, exclude: /node_modules/, loader: 'babel' }
    ]
  },

  // uglify your scriptz
  plugins: [
    new webpack.optimize.UglifyJsPlugin({ minimize: true })
  ]

}

Then you can use them as shown in the HTML snippet above! For an actual live example of this in action, check out my library Remerge.