如何让节点 fs 在 Angular 和 Electron 应用程序中工作

问题描述 投票:0回答:1

“在文件系统上找不到包“fs”,但它内置于节点中。您是否尝试捆绑节点?您可以使用“platform:'node'”来执行此操作,这将消除此错误。”

我遇到了一个问题,我试图让节点 fs 在使用 Electron 和 Angular 的应用程序中读取本地 JSON 文件。通过浏览器运行应用程序可以完美地工作,但是,尝试在 Electron 中运行应用程序会导致上述错误。还有其他人遇到这个问题吗?

角度.json

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "password-manager": {
      "projectType": "application",
      "schematics": {},
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:application",
          "options": {
            "outputPath": "dist/password-manager",
            "index": "src/index.html",
            "browser": "src/main.ts",
            "polyfills": [
              "zone.js"
            ],
            "tsConfig": "tsconfig.app.json",
            "assets": [
              {
                "glob": "**/*",
                "input": "public"
              }
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": [],
            "server": "src/main.server.ts",
            "prerender": true,
            "ssr": {
              "entry": "server.ts"
            }
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kB",
                  "maximumError": "1MB"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kB",
                  "maximumError": "4kB"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "optimization": false,
              "extractLicenses": false,
              "sourceMap": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "configurations": {
            "production": {
              "buildTarget": "password-manager:build:production"
            },
            "development": {
              "buildTarget": "password-manager:build:development"
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n"
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "polyfills": [
              "zone.js",
              "zone.js/testing"
            ],
            "tsConfig": "tsconfig.spec.json",
            "assets": [
              {
                "glob": "**/*",
                "input": "public"
              }
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          }
        }
      }
    }
  }
}

package.json

{
  "name": "password-manager",
  "version": "0.0.0",
  "main": "main.js",
  "scripts": {
    "ng": "ng",
    "start": "ng build --base-href ./ && set NODE_ENV=development && electron .",
    "serve": "ng serve --live-reload && set NODE_ENV=development && electron .",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test",
    "serve:ssr:password-manager": "node dist/password-manager/server/server.mjs"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^18.0.0",
    "@angular/common": "^18.0.0",
    "@angular/compiler": "^18.0.0",
    "@angular/core": "^18.0.0",
    "@angular/forms": "^18.0.0",
    "@angular/platform-browser": "^18.0.0",
    "@angular/platform-browser-dynamic": "^18.0.0",
    "@angular/platform-server": "^18.0.0",
    "@angular/router": "^18.0.0",
    "@angular/ssr": "^18.0.3",
    "dotenv": "^16.4.5",
    "esbuild": "^0.21.5",
    "express": "^4.18.2",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.14.3"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^18.0.3",
    "@angular/cli": "^18.0.3",
    "@angular/compiler-cli": "^18.0.0",
    "@types/express": "^4.17.17",
    "@types/jasmine": "~5.1.0",
    "@types/node": "^18.18.0",
    "electron": "^30.1.0",
    "fs-extended": "^0.2.1",
    "jasmine-core": "~5.1.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "typescript": "~5.4.2"
  }
}
import { WebsiteAccount } from "./WebsiteAccount";
import * as fs from 'fs';
const Accounts = '../password-manager/src/data/accounts.json';

export class Data {

  accounts: WebsiteAccount[];

  constructor() {
    this.accounts = this.read();
    this.write();
  }
  
  private read() {
    var accounts: WebsiteAccount[] = [];


      var jsonString = fs.readFileSync(Accounts, 'utf-8');
      var data = JSON.parse(jsonString);
      var numAccounts = Object.keys(data).length;
      
      for (var i = 0; i < numAccounts; i++) {
          var newWebsite: WebsiteAccount = new WebsiteAccount(data[i].title, data[i].link, data[i].username, data[i].password, data[i].email, data[i].countryCode, data[i].areaCode, data[i].prefixCode, data[i].lineCode);
          accounts.push(newWebsite);
        }

      return accounts;
    
  }

  write() {
    const data = Object.assign({}, this.accounts);
    var dataString = JSON.stringify(data);
    fs.writeFileSync(Accounts, dataString);
  }

  
}

我尝试过使用 fs-extend,尝试将 Angular 设置为浏览器构建模式,并尝试手动将标志设置为 esbuild。

angular electron esbuild node.js-fs
1个回答
0
投票

在我看来,您尝试使用渲染器进程中的

fs

正如文档所述:“渲染器无法直接访问

require
或其他
Node.js
API。为了在渲染器中直接包含 NPM 模块,您必须使用相同的捆绑器工具链(例如,webpack 或 Parcel)您在网络上使用的。” (https://www. Electronjs.org/docs/latest/tutorial/process-model#the-renderer-process

您应该在主进程中定义与文件系统的交互,并使用上下文桥将其公开给渲染器进程。

我最近在一个使用 Angular 和 Electron 的小项目中做了类似的事情。这是我的做法:

electron main process

const { app, BrowserWindow, ipcMain } = require("electron");
const url = require("url");
const fs = require("fs");
const path = require("path");

// Add your window logic here

// Small function to write to the file system
ipcMain.handle("writeData", async (event, data, fileName) => {
  try {
    fs.writeFileSync(fileName, data);
  } catch (error) {
    console.error("Error writing data", error);
  }
})

// Small function to read from the file system
ipcMain.handle("readData", async (event, fileName) => {
  try {
    const data = fs.readFileSync(fileName, "utf-8");
    return data.toString();
  } catch (error) {
    console.error("Error retrieving user data", error);
  }
})

这些是在文件系统中的任何位置读取和写入任何内容的基本功能。这发生在可以访问 fs 的主进程中。我们现在需要使这些函数可供渲染器进程使用。

这是通过预加载脚本中定义的上下文桥(https://www. Electronjs.org/docs/latest/api/context-bridge)完成的(https://www. Electronjs.org/)文档/最新/教程/教程-预加载

要使用上下文桥,我们需要将以下行添加到我们的

BrowserWindow
定义中(在
electron main process
中):

BrowserWindow({
    // Add your specific window definition params
    webPreferences: {
      preload: path.join(__dirname, "preload.js"), // use a preload script
    }
})

preload.js

const { contextBridge, ipcRenderer } = require("electron");
/*
This section provides an interface to read and write files to the user-data directory.
*/
contextBridge.exposeInMainWorld("electronAPI", {
  writeData: (data, fileName) => ipcRenderer.invoke("writeData", data, fileName),
  readData: (fileName) => ipcRenderer.invoke("readData", fileName)
});

方法名称非常隐含,我们将我们的函数提供给外界。我们现在只需要使用它们:

在你的 Angular 应用程序中,很可能是你的数据服务(在电子中

renderer process
):

  /**
   * Write
   */
  storeToDisk(MyAmazingData) {
    window.electronAPI.writeData(
      JSON.stringify(MyAmazingData),
      "MyAmazingFile"
    );
  }

  /**
   * Read
   */
  loadFromDisk() {
    window.electronAPI
      .readData("MyAmazingFile")
      .then((data) => {
        // Handle reading
      });
  }

作为使

electron.window.electronAPI
兼容打字稿的额外奖励:

electronAPI.d.ts

// Declare electron API types to make electron code ts-compliant.
export type ElectronAPI = {
    writeData: (data: string, fileName: string) => Promise<void>,
    readData: (fileName: string) => Promise<string>,
}

declare global {
  interface Window {
    electronAPI: ElectronAPI
    initData: InitData
  }
}

这个解决方案为我解决了这个问题,并且在正确设置后相对容易使用。我想指出,我是电子初学者,可能有一种更简单的方法来执行此操作。

该解决方案异步工作,这是其唯一的缺点。文档似乎暗示这是使用上下文桥的正确方法:

⚠️ WARNING: Sending a synchronous message will block the whole renderer process until the reply is received, so use this method only as a last resort. It's much better to use the asynchronous version, invoke().

https://www. Electronjs.org/docs/latest/api/ipc-renderer#ipcrenderersendsyncchannel-args

© www.soinside.com 2019 - 2024. All rights reserved.