“在文件系统上找不到包“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。
在我看来,您尝试使用渲染器进程中的
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