Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

import undefined when bundling ui library with rollup using yarn workspaces

For a new project, I started using rollup to bundle a UI library and consume that library in a react application. I'm also using yarn workspaces for the internal dependency management between the UI library and the web app.

When I try to use the UI library in my web app, the import returns undefined and throws the "cannot get from undefined" error.

TypeError: Cannot read property 'NavBar' of undefined [0] at App (C:/Users/user/dev/project/packages/project-web/src/pages/App.jsx:9:6)

The webapp code:

import React from 'react';
import {NavBar} from 'project-ui';

const App = () => (
  <div>
    <NavBar/>

    <div>App component!x</div>
  </div>
);

root package.json:

{
  "name": "project",
  "version": "1.0.0",
  "private": true,
  "workspaces": [
    "packages/*"
  ]
}

UI package.json:

{
  "name": "project-ui",
  "version": "1.0.0",
  "main": "dist/project-ui.cjs.js",
  "jsnext:main": "dist/project-ui.es.js",
  "module": "dist/project-ui.es.js",
  "files": ["dist"],
  "scripts": {
    "build": "rollup -c"
  },
  "peerDependencies": {
    "react": "16.3.2",
    "react-dom": "16.3.2"
  },
  "devDependencies": {
    "babel-core": "6.26.3",
    "babel-plugin-external-helpers": "6.22.0",
    "babel-preset-env": "1.6.1",
    "babel-preset-react": "6.24.1",
    "babel-preset-stage-2": "6.24.1",
    "rollup": "0.60.0",
    "rollup-plugin-babel": "3.0.4",
    "rollup-plugin-commonjs": "9.1.3",
    "rollup-plugin-node-resolve": "3.0.0",
    "rollup-plugin-replace": "2.0.0",
    "rollup-plugin-uglify": "4.0.0"
  }
}

web app package.json:

{
  "name": "project-web",
  "version": "1.0.0",
  "scripts": {
    "build": "webpack --colors --display-error-details --config=webpack/webpack.dev.js",
    "dev": "concurrently --kill-others \"npm run dev:start\"",
    "dev:start": "node ./server/index.js"
  },
  "dependencies": {
    "babel-polyfill": "^6.26.0",
    "express": "^4.16.3",
    "react": "^16.3.2",
    "react-dom": "^16.3.2",
    "project-ui": "1.0.0"
  },
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-eslint": "^8.2.3",
    "babel-loader": "^7.1.4",
    "babel-plugin-add-module-exports": "^0.2.1",
    "babel-plugin-dynamic-import-node": "^1.2.0",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.24.1",
    "concurrently": "^3.5.1",
    "eslint": "^4.19.1",
    "eslint-loader": "^2.0.0",
    "eslint-plugin-react": "^7.7.0",
    "piping": "^1.0.0-rc.4",
    "webpack": "^4.6.0",
    "webpack-cli": "^2.0.15",
    "webpack-dev-middleware": "^3.1.3",
    "webpack-dev-server": "^3.1.3",
    "webpack-hot-middleware": "^2.22.1",
    "webpack-node-externals": "^1.7.2"
  }
}

rollup config:

import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel';
import replace from 'rollup-plugin-replace';
import { uglify } from 'rollup-plugin-uglify';
import pkg from './package.json'


const FORMATS = {
  UMD: 'umd',
  ES: 'es',
  CJS: 'cjs'
};
const allowedFormats = [FORMATS.UMD, FORMATS.ES, FORMATS.CJS];

const bundle = (fileFormat, {format, minify}) => {
  if (!allowedFormats.includes(format)) {
    throw new Error(`Invalid format given: ${format}`);
  }

  const shouldMinify = minify && format === FORMATS.UMD;
  const externals = format === FORMATS.UMD
    ? Object.keys(pkg.peerDependencies || {})
    : [
      ...Object.keys(pkg.dependencies || {}),
      ...Object.keys(pkg.peerDependencies || {})
    ];

  return {
    input: 'src/index.js',
    output: {
      file: fileFormat.replace('{format}', shouldMinify ? `${format}.min` : format),
      format,
      name: 'project-ui',
      exports: 'named',
      globals: {
        react: 'React',
        'prop-types': 'PropTypes'
      }
    },
    external: externals,
    plugins: [
      resolve({ jsnext: true, main: true }),
      commonjs({ include: 'node_modules/**' }),
      babel({
        exclude: 'node_modules/**',
      }),
      format === FORMATS.UMD
        ?  replace({'process.env.NODE_ENV': JSON.stringify(shouldMinify ? 'production' : 'development')})
        : null,
      shouldMinify ? uglify() : null
    ].filter(Boolean)
  };
};

export default [
  bundle('dist/project-ui.{format}.js', {format: FORMATS.UMD, minify: true}),
  bundle('dist/project-ui.{format}.js', {format: FORMATS.CJS}),
  bundle('dist/project-ui.{format}.js', {format: FORMATS.ES})
];

the actual generated code from rollup:

import React from 'react';

var NavBar = function NavBar() {
  return React.createElement(
    'header',
    null,
    'nav bar'
  );
};
module.exports = exports['default'];

export { NavBar };

The original navbar:

import React from 'react';

const NavBar = () => (
  <header>
    nav bar
  </header>
);

export default NavBar;

index.js:

export { default as NavBar} from './NavBar/NavBar';

.babelrc:

{
  "presets": [
    ["env", {
      "loose": true,
      "modules": false,
      "targets": {
        "browsers": ["last 2 versions"]
      }
    }],
    "react",
    "stage-2"
  ],
  "plugins": [
    "transform-runtime",
    "add-module-exports",
    "external-helpers"
  ]
}

The generated rollup code looks to be ok, so I'm thinking this is a yarn issue, but I'm not sure. Any help would be appreciated!

Regards Cornel

like image 986
Cornel Janssen Avatar asked Jun 25 '18 06:06

Cornel Janssen


1 Answers

The problem must be in the way you are transpilling the code with babel/rollup. I have a live example in how your code should look in an online babel:

The generated code for me is:

import React from 'react';

const NavBar = () => React.createElement(
  'header',
  null,
  'nav bar'
);

export default NavBar; //  first define default and then we assign export['default']
module.exports = exports['default'];

Observe that in this code we first assign export default to the desired value and then we assign export['defaults'] (when I debug your example I get that export['default'] is undefined, hence you get the error Cannot read property 'NavBar' of undefined [0], since you are passing undefined to exports.

This is done by the plugin 'add-module-exports', that is kind of necessary if you really need module.exports (that shouldnt be needed unless NodeJS or some RequireJS is present).

In order to make it work, just remove "add-module-exports" from your plugins in the project-ui .babelrc.

like image 192
SirPeople Avatar answered Oct 29 '22 11:10

SirPeople