Electron 应用 Module not found: Error: Can't resolve 'fs' 的报错解决

7,024 阅读4分钟

本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力

Electron 简介

Electron(原名为Atom Shell)是GitHub开发的一个开源框架。它通过使用Node.js(作为后端)和Chromium的渲染引擎(作为前端)完成跨平台的桌面GUI应用程序的开发。Electron现已被多个开源Web应用程序用于前端与后端的开发,著名项目包括GitHub的Atom和微软的Visual Studio Code。

Electron 报错症状

上手一个 Electron 项目开始学习写业务代码时,碰到了这样的一个问题。

Node 终端编译报错如下:

Module not found: Error: Can't resolve 'fs' in 'D:\electron\xxx\node_modules\electron'

代码之事,先行谷歌,寻得有两个方案。

方案1,修改 webpack target 配置项

方案出自链接 typescript - Electron 和 typescript : 'fs' can't be resolved,该文作者碰到的报错和本人一模一样。

该文的作者也挺用心的,排版什么的也挺好看的,不晓得颜值咋样。请好好写文章不要多想

方案简而言之,是修改 webpack 的 target 配置项。

要么

module.exports = {
    // ...其他内容略
    { 
        // for files that should be compiled for electron main process 
        target: 'electron-main'
    }
    // ...其他内容略
}

要么

{ 
    // for files that should be compiled for electron renderer process 
    target: 'electron-renderer' 
}

兴致冲冲的去寻找,想去对应修改,发现项目使用的模板 electron-react-boilerplate,其实本身已经配置了该 target 值。 有点撩起来,却又爽约的无奈感。

方案2:修改 webpack externals 配置项

总而言之,也是修改 webpack 的配置项 externals

module.exports = {
  //...其他内容略
  "externals": {
      "fs": 'require("fs")',
  },
  //...其他内容略 
}  

方案链接出自

这个改了以后,的确没有报错了。但又产生了一个新的报错。

Module not found: Error: Can't resolve 'path' in 'D:\electron\xxx\node_modules\electron'

很熟悉有没有,看我习得 ctrl+v 大法。

  "externals": {
      "fs": 'require("fs")',
      "path": 'require("path")',
  },

终于守得云开见月明。Node 侧的构建没有报错了。但浏览器端开始报错了。

Cannot read property 'join' of undefined
Call Stack
 ./node_modules/electron/index.js  
     renderer.dev.js:23020:23Object.options.factory  
     renderer.dev.js:69814:31

报错行见:

const pathFile = path.join(__dirname, 'path.txt');

这个报错倒提醒我了,为啥子 node 端的代码会在浏览器侧执行,是不是哪里姿势不对哦。

最终方案: contextBridge.exposeInMainWorld

找到幺蛾子,原来我直接在 render 进程里面的 js 直接引入了 electron。

const { ipcRenderer, shell, remote } = require('electron');

因为 electron 有很多 node 模块,当然会在浏览器端出错。 这个时候,我们应该用到可 node 可 浏览器端执行的 preload 配置项。

  • 首先:main.js 配置提前加载 preload.js
const createWindow = () => {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
    },
  });
}
  • preload.js 内引入 electron 并通过 contextBridge 暴露给 window
const { contextBridge, ipcRenderer, remote, shell } = require('electron');

contextBridge.exposeInMainWorld('electron', {
  ipcRenderer,
  remote,
  shell,
});
  • render 进程的 js 修改引入方式,通过 window.electron 获取相应的模块
// 注释错误的引入方式
//const { ipcRenderer, shell, remote } = require('electron');
const { shell, remote, ipcRenderer }  = window.electron;
  • 重启项目,重新刷新页面即可。

写在最后

总而言之,出这个问题,还是比较大意。自己没有任何项目经验,自然忽视了 main 主进程(node 端)和 render 渲染进程 (浏览器端)执行环境的差异。看到官网说 ipcRenderer 是渲染进程的模块,就直接在浏览器侧的 js 中直接引入模块了。

记住:如果想在前端环境中使用某些模块,一定要在 preload 选项配置的 js 中,通过 contextBridge.exposeInMainWorld 暴露给 window 给前端使用。

这个出错其实有点像 SSR 的某些类型报错,不该在 node 执行的代码,执行在 node 层了,比如在 node 侧获取 window ,都是执行环境的不匹配导致出错。

当然,这个可能是我的个人情况,不具备通配性。希望对大家有所帮助。

原创不易,感谢大家阅读,感恩点赞支持。

在配置 preload 的时候,碰到了另外一个问题,# Electron App : Unable to load preload script 的解决方式,有兴趣的读者可以阅读。正所谓坑太多了…说多了都是泪。