Like other “serverless” platforms, an OpenWhisk JavaScript Action may be a single .js file. As functions grow beyond trivial—and begin to depend on third-party modules—that single, forlorn .js file can no longer shoulder the burden.

The OpenWhisk Documentation suggests simply throwing an entire project—node_modules and all—into a .zip file. Of course, that’s wasteful and silly—especially if most of node_modules is full of development dependencies.

Now, I’m not one to read documentation unless I get stuck, so I didn’t realize that the docs present an alternative: bundle an Action with webpack. Faced with the unpalatable task of zipping up my entire project dir, I knew I wanted to bundle, but I didn’t reach for webpack—I used Rollup.

Why Rollup?

In this article, Rich Harris (the author of Rollup) writes that webpack’s scope is to help bundle single-page applications (SPAs). Rollup, on the other hand:

“Rollup was created for a different reason: to build flat distributables of JavaScript libraries as efficiently as possible, taking advantage of the ingenious design of ES2015 modules.”

—Rich Harris

The key for us in the above quote is “flat distributable.” We want to upload our Action as a single .js file. That is precisely what Rollup provides—with little extra ornamentation.

Rollup doesn’t do stuff like code splitting nor hot module replacement. We don’t need these features anyway, since we’re not bundling SPAs. Heck, we don’t even need ES modules all the way down (though we won’t get all of the benefits of Rollup’s tree-shaking abilities)—its plugin ecosystem has us covered.

Read on for an example configuration.

An Example Action Using Rollup

Here’s the lovely example action which the OpenWhisk docs provide. It’s intended to be uploaded within .zip file which also includes node_modules and everything else in its project folder:

This may seem a little weird, but note that OpenWhisk executes our .js file as if it were at the top level (no, I’m not sure why). The webpack example in OpenWhisk’s docs make this explicit by assigning the function to global.main; const main = myAction is equivalent.

However, since Rollup aggressively tree-shakes, casually assigning myAction to an unused variable is verboten, and myAction would be trashed. This is also why we can’t just write export {myAction as main}; it doesn’t create the global variable converted to CommonJS module format by Rollup. To address this, just exportmain; we can use it later when we write our tests!

Even though our dependencies don’t need to use ES modules, our sources do, so we importleft-pad at the top. Then, myAction will become the default export. We’ll see how this works in the Rollup config below.

This configuration declares we will use CommonJS (Node.js-style; require() and module.exports, exports, etc.).

CommonJS (cjs) format isn’t actually required by OpenWhisk, as an IIFE or UMD bundle would work, but actually to suppress annoying warnings; if we don’t use cjs, it will assume we are bundling for a browser and take exception to what we’re trying to do.

We wanted to bundle left-pad, yet it didn’t happen; our bundle calls require('left-pad') like it’s in a node_modules/ somewhere. This won’t do; we only want to upload dist/my-action.js, and no part of node_modules/. Where’s the beef?

Third-Party Modules in The Environment

Practically speaking, this means we don’t need to bundle these modules. They can be ignored, just like a built-in, as shown above. We add any which we're using to the externalArray in rollup.config.js.

It's still helpful to npm install any of these which we’re using (for code completion, testing, etc.). They must be added to the externalArray, regardless.

Alternatively, use rollup-plugin-auto-external with option {dependencies: false}; then add the modules (as minimatch globs) which we do want to bundle to the Arrayinclude property of the commonjs plugin’s configuration object. Here’s an example of a Rollup config where we consume the pre-installed request-promise module, but exclude it from the bundle:

import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
export default {
// (imagine there is more configuration here)
plugins: [
resolve(),
commonjs({
// left-pad is the ONLY dependency which is bundled
include: ['node_modules/left-pad/**']
}),
autoExternal({
// continue to exclude "util", or any other built-in
builtins: true,
// default is true, but we still must bundle left-pad, so this is false.
dependencies: false
})
],
// request-promise is a dependency in package.json
external: ['request-promise']
}

So far, I’ve found Rollup works just as well as webpack for OpenWhisk Action deployment. I don’t see a clear winner, other than they both beat .zip files. Until I do, I’ll probably continue using Rollup, just ‘cause. What I’d really like to see is a zero-configuration, purpose-built bundler for Node.js OpenWhisk actions. Hmmm…