create-react-app: npm run build fails for EventEmitter

@gaearon asked that I file a separate issue.

How to reproduce: run create-react-app fail-event-emitter, add a line

import EventEmitter from 'events';

to src/index.js and then run npm run build, which fails with:

> fail-event-emitter@0.1.0 build /home/gback/fail-event-emitter
> react-scripts build

Creating an optimized production build...
Failed to compile.

static/js/main.068c41be.js from UglifyJs
Unexpected character '`' [/usr/lib/nodejs/events.js:260,0]

react-scripts 0.7.0 is used and node.js 6.9.1 (or 7.0)

The purported cause is that CRA does not transpile “dependencies” into ES5 and then the UglifyJS plugin fails when it encounters ES6 code. The test case above was derived from a failure in react-bootstrap-tables, which includes a module that in turn references EventEmitter. Others have suggested to provide an alternate EventEmitter implementation in the application path, but it is not clear to me how react-bootstrap-table’s dependencies can be made to use this alternate implementation.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 2
  • Comments: 130 (54 by maintainers)

Most upvoted comments

Ha!

I just did

npm install events --save

in my directory and that worked! Am I dumb for not trying this right away?

And why does it work? I thought the way npm works is such that a package like recharts gets its own, or the default version of ‘events’ that the developer relied upon when building it… why can I override it in this way?

Very reminiscent of 2000’s Java CLASSPATH hell.

Fixed in #1194.

Nope. Not from source but from NodeSource Node.js Binary Distributions. The recommended way to install recent Node.js versions on Linux is to use setup scripts (like this for Debian / Ubuntu) which set up their repo on the system so Node.js can be installed and updated through the system’s package manager.

events.js uses template strings from node 6.0.0. They were introduced in c6656db.

How about we intentionally deviate and only respect relative NODE_PATH. That’s what our users use it for anyway. And global is always dangerous.

I have a working theory. If webpack attempts to resolve a module name such as events or timers it will use the node-libs-browser shims if and only if those names are still unresolved after all of root, modulesDirectories, and fallback have been searched.

I’m basing this on these observations:

(1) NodeSourcePlugin (code) calls compiler.resolvers.normal.apply in “after-resolvers”

	compiler.plugin("after-resolvers", function(compiler) {
		var alias = {};
		Object.keys(nodeLibsBrowser).forEach(function(lib) {
			if(options[lib] !== false)
				alias[lib + "$"] = getPathToModule(lib, options[lib]);
		});
		if(Object.keys(alias).length > 0) {
			compiler.resolvers.normal.apply(
				new ModuleAliasPlugin(alias)
			);
		}
	});

(2) WebPackOptionsApply (code)

    compiler.resolvers.normal.apply(
...
        makeRootPlugin("module", options.resolve.root),
        new ModulesInDirectoriesPlugin("module", options.resolve.modulesDirectories),
        makeRootPlugin("module", options.resolve.fallback),
...
    );
...
    compiler.applyPlugins("after-resolvers", compiler);

This would explain why placing a shim manually in node_modules works even on Linux systems that have the built-in modules in NODE_PATH, and it would explain why on Macintoshes that don’t set NODE_PATH the browser shim is found as well.

FWIW, the documentation of NodeSourcePlugin refers to it as providing polyfills - which generally means they are meant to be used only as a last resort.

If my theory is correct, then either the nodePaths patch should be reversed, or amended to include warnings if users’ NODE_PATH contains the locations of node’s built-in modules. On the other hand, we can’t really tell if a user’s NODE_PATH setting is intentional or not. It could be that a future version of node.js provides a perfectly usable module ‘xyz’ in /usr/lib/nodejs and the user wants to include it in their build.

Maybe have a separate variable “RESPECT_NODE_PATH” for the use cases where NODE_PATH should be respected, and don’t respect it by default.

We plan on documenting this on our new docs but I wanted to leave a diagram that may or may not assist in understanding the waterfall style of enhanced resolve:

image

Side note: all the code in enhanced-resolve that handles this seems to be written by @sokra - but you already knew this 😃 Maybe he could explain the normative behavior?

My specific question. In this code we see how the options given to webpack’s resolve control the resolution process.

It seems to me that fallback directories should be considered first. I think not specifying a root is user error?

@jimmyhmiller adding the ownNodeModules directory as root works for me too even when NODE_PATH/fallback is set.

While this discussion has moved to NODE_PATH, isn’t the core issue here fact that the global copy of a module is being used in place of the copy installed as a project dependency? Why is it falling back (as @godmar confirmed) to the native version at all when it exists right there in node_modules as a dependency of a root module? Would testing with webpack 2 be the only way to help us determine if the root cause is indeed webpack?

Because this defeats the purpose of not hardcoding support absolute paths inside webpack: tool compatibility. Right now things “just work” in IDEs/linters/Flow/etc that respect Node resolution mechanism.

Yes we are using it for that. The problem is that the ‘src/node_modules’ requires us to do refactor on our structure where as the environment variable would allow us to make one change in one place. But also the src/node_modules solutions just doesn’t seem to really address the issue.

The goal is to be able to not use any relative imports. Relative imports are incredibly fragile and annoying to work with. When we want to do refactoring of structure, relative imports are a real pain.

I’m trying to track this down.

It appears that ‘enhanced-resolver’ is somehow involved as it is used by webpack. I added the following statement to node_modules/enhanced-resolve/lib/Resolver.js about here

...
        return this.applyPluginsParallelBailResult(types[0], request, createInnerCallback(function innerCallback(err, result) {
if (result) console.log(`Response to ${currentRequestString} is ${result.path}`);
            if(callback.log) {
...

This shows:

Response to events in /home/gback/fail-event-emitter2/src is /home/gback/fail-event-emitter2/node_modules/events/events.js
Response to sys in /home/gback/fail-event-emitter2/src is /usr/lib/nodejs/sys.js

A difference between the two is that ‘sys’ is provided by ‘util’ whereas ‘events’ is provided by ‘events’. So whatever aliasing/renaming node-libs-browser does is not applied.

BTW, the version of enhance-resolve that the webpack CRA includes uses is pretty old.

Output of npm list: https://gist.github.com/godmar/b04b96e9648f8464c983c754482af8ab

Adding my versions for completeness:

$ create-react-app --version
create-react-app version: 0.6.0
$ node -v
v6.9.1
$ npm -v
4.0.3

So is there a solution that does not require dropping/commenting out UglifyJS?

To be clear the problem is not in UglifyJS. It’s in Webpack including ES6 code from Node instead of shims in some cases for some reason.

Even if we didn’t use UglifyJS it would still be very bad to ship ES6 code from Node to users who may not have recent browsers.

Tagging my self to track this.