Exporting ES6 Library Modules as Global Scripts with Webpack

March 16, 2016

After author­ing a library (Remerge) using ES6 style mod­ules, aside from con­sump­tion with npm, I also wanted to release it as a stand­alone script so that users could use it by simply includ­ing it with a simple script tag - well, if they wanted to, at least (appeal­ing to the lowest common denom­i­na­tor here heh).

The Sce­nario

So I wanted to be able to do some­thing 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 slight­ly dif­fer­ent export pat­tern. 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 (tack­ling the gen­er­al case here):

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

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

The Prob­lem

The prob­lem is that, as it is, bundling these ES6 mod­ules will result in objects that look like this:

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

which is tech­ni­cal­ly usable, but to use it would look like:

var something = mainFunction.default(...)

which is a real PITA for ensur­ing a con­sis­tent API.

The Fix

The fix for this is simple: simply write a file that imports those ES6 mod­ules the ES6 way, but exports them in nodeJS module.exports style. Web­pack will then handle them cor­rect­ly!

For the top-level func­tion export, simply export the func­tion as is:

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

module.exports = mainFunction

For the object whose prop­er­ties should be util­i­ty func­tions, the solu­tion is sim­i­lar­ly straight­for­ward - simply create an (in this case anony­mous) object whose prop­er­ties are those func­tions:

// 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 pur­pose is for Web­pack to gen­er­ate the script files. You can place them any­where it makes sense.

The Web­pack con­fig­u­ra­tion for han­dling this is very simple (as far as build con­figs 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 snip­pet above! For an actual live exam­ple of this in action, check out my library Remerge.