Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Electron+Webpack = Module not found: Error: Can't resolve fsevents/fs/etc in chokidar/etc

I'm trying to create an Electron app, but many node-libs causes the Module not found error, even for the electron-main target.

webpack.config.js

const path = require('path')

module.exports = {
  mode: 'development',
  target: `electron-main`,
  entry: {main: `./src/main.js`},
  resolve: {
    extensions: ['.js'],
    modules: ['node_modules', path.join(__dirname, 'src')],
  },
  output: {
    path: path.resolve(__dirname, `dist`),
    filename: '[name].bundle.js',
  },
}

src/main.js

const watcher = require('chokidar').watch('./dist')

watcher.on('change', function() {
  console.log('change', arguments)
})

package.json

{
  "name": "test",
  "version": "1.0.0",
  "author": "I",
  "private": true,
  "main": "dist/main.bundle.js",
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "@types/chokidar": "^1.7.5",
    "chokidar": "^2.0.4",
    "electron": "^2.0.8",
    "webpack": "^4.17.1",
    "webpack-cli": "^3.1.0"
  }
}

This produces the error during build:

WARNING in ./node_modules/chokidar/lib/fsevents-handler.js
Module not found: Error: Can't resolve 'fsevents' in '.\node_modules\chokidar\lib'
 @ ./node_modules/chokidar/lib/fsevents-handler.js
 @ ./node_modules/chokidar/index.js
 @ ./src/main.js

What am I doing wrong?

PS: Adding node: { fsevents: 'empty' } doesn't help.

like image 521
J. Snow Avatar asked Nov 17 '22 03:11

J. Snow


1 Answers

tl;dr

Tell Webpack to ignore that require via IgnorePlugin:

const { IgnorePlugin } = require('webpack');

const optionalPlugins = [];
if (process.platform !== "darwin") { // don't ignore on OSX
  optionalPlugins.push(new IgnorePlugin({ resourceRegExp: /^fsevents$/ }));
}

module.exports = {
  // other webpack config options here...
  plugins: [
    ...optionalPlugins,
  ],
};

Explanation

The issue comes from this line in lib/fsevents-handler.js:

try {
  fsevents = require('fsevents'); // this module doesn't exists on Linux
} catch (error) {
  if (process.env.CHOKIDAR_PRINT_FSEVENTS_REQUIRE_ERROR) console.error(error);
}

My guess is that since it is a require, Webpack tries to resolve it, even tho on Linux it doesn't exists. That's why it is in a try...catch block, because it will fail when running on Linux and they handle it that way. Sadly Webpack (occasionally?) has problems with this.

If we check how this whole fsevents-handler.js module is used in the main index.js file, we can see that they check if they can use this module via FsEventsHandler.canUse(), save it to opts.useFsEvents, then every time they want to use that module they check that option.

The canUse function is implemented this way:

const canUse = () => fsevents && FSEventsWatchers.size < 128;

So if we had a module that returned something falsy, then through the fsevents variable the whole thing would be disabled (as it should be by default on Linux).

Solution

After I created the (now alternative) solution described in the chapter below, vinceau came up with a simpler solution: you can tell Webpack to ignore requires based on a regex trough IgnorePlugin. This way you don't have to mock the module so the require in the lib will work (fail) as the authors originally expected it. So just add the plugin:

module.exports = {
  plugins: [
    new IgnorePlugin({ resourceRegExp: /^fsevents$/ }),
  ],
};

This will ignore the require on every OS, but that shouldn't happen under OSX so it is better to check the platform and only add it optionally:

const optionalPlugins = [];
if (process.platform !== "darwin") { // don't ignore on OSX
  optionalPlugins.push(new IgnorePlugin({ resourceRegExp: /^fsevents$/ }));
}

module.exports = {
  plugins: [ ...optionalPlugins ],
};

Alternative Solution

We can create a mock module that will return a falsy default (also add some comments to explain the situation):

// mock module to prevent Webpack from throwing an error when
// it tries to resolve the `fsevents` module under Linux that
// doesn't have this module.
// see: https://stackoverflow.com/a/67829712

module.exports = undefined

Lets save this to the project root named fsevent.js (you can call/place it however you like), then configure Webpack, so when it tries to resolve fsevents it will return this mock module. In Webpack 5 this can be done trough resolve aliases. We also add a condition so we don't do this on OSX where fsevents is available:

const optionalAliases = {};
if (process.platform !== "darwin") {
  optionalAliases['fsevents$'] = path.resolve(__dirname, `fsevents.js`);
}

module.exports = {
  resolve: {
    alias: {
      ...optionalAliases,
    },
  },
};
like image 90
totymedli Avatar answered Apr 26 '23 05:04

totymedli