Accessing filesystem in Angular 2 app using Electron - node.js

I know that Angular 2 is run on a web browser, which does not have access to the file system.
However, I'm using Electron as my front-end, and also running the app via electron:
"build-electron": "ng build --base-href . && cp src/electron/* dist",
"electron": "npm run build-electron && electron dist"
Therefore, I run it with npm run electron which at the very end runs electron dist.
Since I'm running through electron and not ng I would think that I should be able to access the filesystem. However, when I do:
import * as fs from 'fs'
I get an error:
ng:///AppModule/AppComponent_Host.ngfactory.js:5 ERROR TypeError: __WEBPACK_IMPORTED_MODULE_0_fs__.readFileSync is not a function(…)
Similarly, when I try: var fs = require('fs');
I get:
ng:///AppModule/AppComponent_Host.ngfactory.js:5 ERROR TypeError: fs.readFileSync is not a function
This is the call resulting in the error:
this.config = ini.parse(fs.readFileSync('../../CONFIG.ini', 'utf-8'))
Does anyone have any idea what's causing this?
Thanks.

Solved it by:
1) Eject webpack: ng eject
2) Add target: 'electron-renderer' to the module.exports array inside webpack.config.js
3) Require remote, since we're in the renderer, but fs is only available in the main process (Read more): var remote = require('electron').remote;
4) Require fs (this time using remotes implementation of require): var fs = remote.require('fs');
And now it works!

I am using
Angular CLI: 7.0.7
Node: 8.12.0
OS: win32 x64
Angular: 7.0.4
I tried the ng eject method it didn't work in my case, it is disabled by default and will be removed completely in Angular 8.0
Error message: The 'eject' command has been disabled and will be removed completely in 8.0.
It worked for me by creating a file called native.js in the src folder and insert the following:
`window.fs = require('fs');
Add this file to the angular-cli.json scripts array:
"scripts": [
"native.js"
]
Add the following lines to polyfills.ts:
`declare global {
interface Window {
fs: any;
}
}`
After that you can access the filesystem with:
`window.fs.writeFileSync('sample.txt', 'my data');`
credits

As I understand it, you build the application with Webpack.
You can expose all Node modules via the externals array in your webpack config.
module.exports = {
"externals": {
"electron": "require('electron')",
"child_process": "require('child_process')",
"fs": "require('fs')",
"path": "require('path')",
...
}
}
Since they are provided through the Webpack externals, one does not have to require them but use them with imports.
import * as fs from 'fs'
You can read more about this problem in my article.

I'm late to the party but I also stumbled upon this problem recently. To the late comers, you can use ngx-fs
https://github.com/Inoverse/ngx-fs
Usage:
const fs = this._fsService.fs as any;
fs.readdir("\\", function (err, items) {
if (err) {
return;
}
for (let i = 0; i < items.length; i++) {
console.log(items[i]);
}
});

I had the same problem and could solve it in an easier way:
Just download this project as start, the 'require'-s are already in the webpack.config.js file (along with the integration of angular, electron and so on):
https://github.com/maximegris/angular-electron
import 'fs' into home.ts (or into any other component) as mentioned by #Matthias Sommer above:
import * as fs from 'fs'
Use 'fs' :)

Related

React UnhandledSchemeError - "node:buffer" is not handled by plugins

I'm trying to use a package in my React project that will allow me to make API calls (axios, node-fetch, got etc.)
When these packages are not installed, the app runs properly. When any of them are installed and called in the code, I'm facing the error as follows:
Ignoring the warnings, I believe the problem has its roots from the output below:
Failed to compile.
Module build failed: UnhandledSchemeError: Reading from "node:buffer" is not handled by plugins (Unhandled scheme).
Webpack supports "data:" and "file:" URIs by default.
You may need an additional plugin to handle "node:" URIs.
I tried everything. Reinstalled node_modules. Created a clean test app, tried there. Also did my research, didn't find any relevant, clear solution on this. Nothing helped.
What am I doing wrong??
DomException file content:
/*! node-domexception. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
if (!globalThis.DOMException) {
try {
const { MessageChannel } = require('worker_threads'),
port = new MessageChannel().port1,
ab = new ArrayBuffer()
port.postMessage(ab, [ab, ab])
} catch (err) {
err.constructor.name === 'DOMException' && (
globalThis.DOMException = err.constructor
)
}
}
module.exports = globalThis.DOMException
npm version: 8.5.5
node version: 16.15.0
You can work around this with this Webpack configuration:
plugins: [
new webpack.NormalModuleReplacementPlugin(/node:/, (resource) => {
resource.request = resource.request.replace(/^node:/, "");
}),
]

Use node Path module with angular 6

I'm trying to use the module Path in an Angular 6 project.
I found this post to fix the issue :
https://gist.github.com/niespodd/1fa82da6f8c901d1c33d2fcbb762947d
it says to add a script :
const fs = require('fs');
const f = 'node_modules/#angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js';
fs.readFile(f, 'utf8', function (err,data) {
if (err) {
return console.log(err);
}
var result = data.replace(/node: false/g, 'node: {crypto: true, stream: true}');
fs.writeFile(f, result, 'utf8', function (err) {
if (err) return console.log(err);
});
});
And declare it in package.json :
{...
"scripts": {
"postinstall": "node patch.js",
...
}
}
But when I'm trying to use it in a service, just import it like this :
import {join} from 'path';
It says that the module Path cannot be found.
How can I correct this ?
Interesting problem.
I managed to get Path module work on my Angular project.
Here are the steps.I use node 8, angular 6.
1: install path.
npm install path
This is an exact copy of the NodeJS ’path’ module published to the NPM registry.
2, I also installed #types/node as in Angular we are using typescript.
Although later I removed this module and path module seems still works.
3, run the above script using
node patch.js
I manually run it and go to 'node_modules/#angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js' to check the file actually changed.
4, I put
import {join} from 'path';
in one of my component.ts file
let x = join('Users', 'Refsnes', '..', 'demo_path.js');
console.log("-------------------------------------------------");
console.log(x);
in a Component's onInit() function.
and run "ng serve"
I saw the expected output in my console when loading the webpage.
-------------------------------------------------
Users/demo_path.js
So this method does work. I am not sure which step you did wrong. My guess would be the first step as I tried if not do step 3 there's different error message. Please check your node_modules folder and verify path folder exists and reinstall it if necessary.
Be sure to have node types installed: npm install --save-dev #types/node
Import path: import * as path from 'path';

Redux-Observable breaks on server-side rendering

I have a test repo with redux-observable
It works with webpack-dev-server but breaks with server-side-rendering giving:
TypeError: action$.ofType(...).delay is not a function
How to reproduce:
yarn dev works okay (webpack-dev-server).
yarn build && yarn start - runs node server-side-rendering which is breaking when creating store with redux createStore method.
It recognizes imported operators from rxjs within a browser (webpack-dev-server). My guess it might be a problem with webpack serverConfig, more specifically with:
externals: fs.readdirSync('./node_modules').concat([
'react-dom/server',
]).reduce((ext, mod) => {
ext[mod] = `commonjs ${mod}`;
return ext;
}, {}),
importing the whole rxjs library will jeopardise your tree shaking.
use pipe instead.
import { delay } from 'rxjs/operators';
const epic = action$ => action$
.ofType('baz')
.pipe(delay(5000))
.mapTo({ type: 'bar' });
;
It turned out I had to include rxjs in server.js where express is like:
import `rxjs`;
But I would swear I tried that solution before I posting a question.

Jest can not deal with insertAdjacentElement?

I want to test a quite simple JS function
export function displaySpinner() {
const loadingOverlayDOM = document.createElement('DIV');
const spinner = document.createElement('IMG');
loadingOverlayDOM.id = 'overlay-spinner';
loadingOverlayDOM.className = 'content-overlay';
spinner.className = 'is-spinning';
spinner.setAttribute('src', '/assets/img/svg/icons/spinner.svg');
l loadingOverlayDOM.insertAdjacentElement('beforeend', spinner);
document.body.insertAdjacentElement('beforeend', loadingOverlayDOM);
}
with this (for the purpose of this issue stripped down) Jest test code:
test('displaySpinner displays the spinner overlay in the current page', () => {
utils.displaySpinner();
});
But the test run yells at me:
FAIL app/helper/utils.test.js
● utils › displaySpinner displays the spinner overlay in the current page
TypeError: loadingOverlayDOM.insertAdjacentElement is not a function
at Object.displaySpinner (app/helper/utils.js:185:439)
at Object.<anonymous> (app/helper/utils.test.js:87:15)
at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
at process._tickCallback (internal/process/next_tick.js:109:7)
Is this an error in Jest or am I missing something here?
I finally found the answer myself:
Jest uses jsdom which does not yet support the DOM function insertAdjacentElement (see this issue on GitHub and it's references). So I'll have to wait until jsdom implements it or use another method in my JS.
You can replace the default version of jsdom with an up-to-date version (e.g. 14) by installing the corresponding module:
npm install --save-dev jest-environment-jsdom-fourteen
or using yarn:
yarn add jest-environment-jsdom-fourteen --dev
and then using the jest testEnvironment config parameter:
{
"testEnvironment": "jest-environment-jsdom-fourteen"
}
Note that if you launch jest with the --env=jsdom argument, this will override the config file, so you need to remove it.

Node cannot find module "fs" when using webpack

I'm using node.js and webpack to create a bundle. From what I've read, node.js should contain fs module for managing files. However when I call require("fs") I get an Cannot find module "fs" error. What should I do?
I came across this problem myself when bundling with webpack and found the answer on this thread.
The way to solve it for me was to use the following config:
module.exports = {
entry: "./app",
output: {
path: __dirname,
filename: "bundle.js"
},
module: {
loaders: [
{
test: /\.js$/,
exclude: 'node_modules',
loader: 'babel',
query: {presets: ['es2015']},
}
]
},
target: 'node'
};
By setting target to node webpack will make the necessary changes to bundle your node application
Edit: This answer targeted webpack 1.x which has now been superseded.
If you are running your webpack bundle in nodejs environment then target: 'node' is required in webpack.config.js file otherwise webpack takes default value as web for target check here.
You can resolve the issue in two ways
Add below configuration to your webpack.config.js
node: {
fs: "empty"
}
OR
Add below configuration to your package.json
"browser": {
"fs": false
}
Edit:
promising fix is
"browser": {
"fs": false
}
I had the same issue when bundling a NWjs application using webworkers (which in turn had node enabled).
The solution I found was to include each native module I used in externals with the prefix commonjs to the name of the module. For example:
...
target: "webworker", // or 'node' or 'node-webkit'
externals:{
fs: "commonjs fs",
path: "commonjs path"
}
...
I've done the same for targets "webworker" and "node-webkit" in different projects to solve the same issue.
webpack nwjs webworker nodejs node
Add below configuration to your webpack.config.js
resolve: {
fallback: {
fs: false
}
}
I needed to build a class that would use fetch if executed in a browser, or fs if executed in node. For other reasons, it was impractical to produce separate bundles, so I produced a single browser-targeted bundle.
The solution I used was to use eval('require("fs")') if the script was running in node.
const fs = eval('require("fs")')
Browser-safe (fs is null in the browser):
const fs = typeof window === 'object'
? null
: eval('require("fs")')
After trying everything I found on the internet (target, externals, node configs), the only solution that actually worked for me was replacing:
const filesystem = require("fs")
or
import fs from "fs"
by the special webpack version
const fs = __non_webpack_require__("fs")
This generates a require function that is not parsed by webpack.
In addition to the answer of PDG
I'm used to this short copy/paste candy.
Using path and fs :
var nodeModules = {};
fs.readdirSync(path.resolve(__dirname, 'node_modules'))
.filter(x => ['.bin'].indexOf(x) === -1)
.forEach(mod => { nodeModules[mod] = `commonjs ${mod}`; });
// Before your webpack configuration
module.exports = {
...
}
Then inside your configuration file, include the nodeModules variable in the externals
...
externals: nodeModules,
...
It would be more elegant to use pre-defined solution as:
Adding target: 'node' to webpack config file.
More info on: official documentation
For the solution we are building we had to force an older version of webpack:
npm install --save --force webpack#webpack-3

Resources