The Mysterious Case of Emotion and "exports is not defined"
Tags: javascript, emotion, babel, pagespeed
Published: 2021-11-03 18:30:07

Thought I’d share a bug I ran into a while back that sent me on a Poirot style investigation full of red herrings and unexpected culprits.

This is tangentially related to my recent page speed woes at work. We’d started using [Emotion](https://emotion.sh/docs/introduction) for CSS-in-JS in our component library and, combined with lazy and conditional component loading, it helped with some of the “Reduce unused CSS” warnings we were seeing in Lighthouse.

So adding Emotion as a styling option in our main codebase seemed like an obvious choice. We’d already installed `@emotion/core` (v10) when we started importing from our component library which meant it should be a quick, two-step process: 1) running `yarn add @emotion/babel-preset-css-prop` and 2) adding it to our `babel.config.js` presets after `@babel/preset-react`. I followed those steps, ran Webpack, and promptly got the error "ReferenceError: exports is not defined".

Weird.

That sent me on a lengthy wild goose chase. Stack Overflow had [one question](https://stackoverflow.com/questions/54272194/emotion-css-in-js-referenceerror-exports-is-not-defined) with no accepted answer. [Babel](https://babeljs.io/) was the primary suspect so I tried reordering all my presets and then upgrading to the latest version. I poked around in the Webpack config. The [Emotion repo’s issues](https://github.com/emotion-js/emotion/issues) page wasn’t any help. I tried completely switching up our Emotion implementation but that created a whole new set of problems.

After hours of running around in circles, I finally went back to that first Stack Overflow link. What did my `babel.config.js` have in common with the `babel.config.js` posted there? The `@babel/plugin-transform-modules-commonjs` plugin. Searching for `@babel/plugin-transform-modules-commonjs` and “exports is not defined” got a ton of hits and revealed that `@babel/plugin-transform-modules-commonjs` is a pretty common answer to the question “How do I fix an ‘exports is not defined’ error?” Finally a clue! Now why did it stop working?

It turns out plugin/preset order can be pretty important in Babel. `@babel/plugin-transform-modules-commonjs` needed to run after `@emotion/babel-preset-css-prop` but plugins always run first. Since presets are just collections of plugins, I tried uninstalling `@emotion/babel-preset-css-prop`, looking at its [source code](https://github.com/emotion-js/emotion/blob/main/packages/babel-preset-css-prop/src/index.js), and installing each plugin individually. So my `babel.config.js` went from looking something like this:

```js
{
  presets: [
    ...
    '@babel/preset-react',
    '@emotion/babel-preset-css-prop'
  ],
  plugins: [
    ...
    '@babel/plugin-transform-modules-commonjs'
  ]
}
```
to more like:
```js
{
  presets: [
    ...
    '@babel/preset-react'
  ],
  plugins: [
    ...
    [
      '@emotion/babel-plugin-jsx-pragmatic',
      {
        export: 'jsx',
        module: '@emotion/core',
        import: '___EmotionJSX'
      }
    ],
    [
      '@babel/plugin-transform-react-jsx',
      {
        pragma: '___EmotionJSX',
        pragmaFrag: 'React.Fragment'
      }
    ],
    'emotion',
    '@babel/plugin-transform-modules-commonjs'
  ]
}
```
And that solved the mystery. It took a while but I did learn *a lot* more about Babel.