How to Import Modules Out of Webpack's Scope
Tags: webpack, javascript, productivity, babel
Published: 2019-02-08 00:38:29

**TL;DR:** [check out the solution](#the-fix)

Consider this scenario: you work on a host of projects that all require a set of JavaScript modules, and you want to track and import the common modules from a centralized location. So, you begin to move over these common modules to their own repository; the idea is no more copy-pasting on every change, just import from this new repo!

In this scenario, assume the individual projects use the common workflow of Webpack and Babel to transpile and build `.js` bundles. Webpack has a great feature that allows aliasing of modules in a given path under a particular identifier, rather than `'../path/to/commons/module.js'`, for example. This also works with modules outside the scope of the Webpack config file; aliasing can be configured under the `resolve.alias` object:

```javascript
// webpack.config.js
const path = require("path");

module.exports = {
  // ...
  resolve: {
    alias: {
      commons: path.resolve(__dirname, "../path/to/commons")
    }
  }
  // ...
};
```

To import these `commons` modules, the import statement in one of the projects will have to be updated to use this alias:

```javascript
// index.js
import { getAvgLength } from "commons/stringUtils";
```

For this scenario, let's say the `commons` directory has just one file now, the `stringUtils.js` file imported above; that file looks like this:

```javascript
// commons/stringUtils.js
export function getAvgLength(...strings) {
  const stringCount = strings.length;
  const combinedStringLengths = strings.reduce((total, str) => {
    return total + str.length;
  }, 0);
  return combinedStringLengths / stringCount;
}
```

Great utility, right? So the commons directory is intialized with one module, Webpack is aliasing the `commons` directory in a project directory, and the proper import statement is set up in that project's main JavaScript file. Now, Webpack should be restarted, and a bundle would be successfully built; restarting the browser to get this latest build would show that everything is setup, running smoothly, and no more considerations need to happen.

Except, that's not necessarily the case.

### The Problem

What's actually happening is the module being imported from `commons` isn't _actually_ being loaded by Webpack and transpiled by Babel; it is being imported and placed in the bundle, able to be utilized normally, but that's it. Any modules located outside the scope of the `webpack.config.js` are being imported and bundled without any additional transformation; this is what a section of the `bundle.js` would look like:

```javascript
/***/ "../path/to/commons/stringUtils.js":
/*!*******************************!*\
  !*** ../path/to/commons/stringUtils.js ***!
  \*******************************/
/*! exports provided: getAvgLength */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getAvgLength", function() { return getAvgLength; });
function getAvgLength(...strings) {
  const stringCount = strings.length;
  const combinedStringLengths = strings.reduce((total, str) => {
    return total + str.length;
  }, 0);
  return combinedStringLengths / stringCount;
}

/***/ }),
```

Unless the `commons` code is using features that are not supported by browsers yet (or for example, a type annotation system like TypeScript or Flow), no problems will be apparent until a build process is initiated or the code is tested in a browser where some of the `commons` features are not supported. If the code being imported complies with the target environment's supported ECMAScript version (most likely ES5), then this will likely not be a problem, so this only affects ES2015+ code not in the same directory as `webpack.config.js`.

### The Fix

No amount of tweaks or updates to the project's Webpack or Babel config files will help resolve this issue; the solution is to go to the source. In the `commons` directory, setup a script that runs all JS files through Babel, which should be run when making changes to the common modules; this can be done by adding a `package.json`, some form of Babel configuration (seen below as a property in the package.json file), and installing `@babel/cli`, `@babel/core`, and `@babel/preset-env`:

```javascript
// commons/package.json
{
  "scripts": {
    "babel": "babel entry.js -d dist/"
  },
  "babel": {
    "presets": [
      [
        "@babel/env",
        {
          "targets": {
            "browsers": ["last 2 versions"]
          }
        }
      ]
    ]
  },
  "devDependencies": {
    "@babel/cli": "^7.2.3",
    "@babel/core": "^7.2.2",
    "@babel/preset-env": "^7.3.1"
  }
}
```

When `yarn babel`/`npm run babel` is initiated, Babel will transpile all the files matching a glob pattern (in the above example, it only transpiles `entry.js`) and places the result in `dist/`. In order for the projects to import the correct, transpiled code, update the Webpack's `commons` alias to point to that `dist` directory:

```javascript
// webpack.config.js
// ...
resolve: {
  alias: {
    commons: path.resolve(__dirname, "../path/to/commons/dist");
  }
}
// ...
```

When Webpack is restarted or a build is initiated, the bundle should now only output code transpiled down to whatever the Babel settings are setup to output. Problem solved!

---

I am by no means an expert on the Webpack/Babel workflow, and so I do not know for a fact where in the pipeline the issue lies, but I suspect the breaking point is somewhere in `babel-loader`, since Webpack is doing its job of importing the modules. Regardless, I did not write this article to point fingers, but to bring awareness and present a solution.

I came across this exact issue while trying to create a common directory for my JavaScript modules at work, and upon running a build, finding that none of the ES2015+ code was being transpiled. Hopefully this helps someone out there thinking of using a similar pattern of development and organization!