Can't resolve 'fs' in \node_modules\electron - javascript

I am using the electron-react boilerplate and want to use an electron dialog in App.tsx:
const { dialog } = require('#electron/remote') //also tried with import
const Hello = () => {
const readFromFile = async () => {
dialog.showOpenDialog({})
}
return (
<>
<button onClick={() => readFromFile()} >Test</button>
</>
)
}
in main.ts I placed the following line at the top
require('#electron/remote/main').initialize()
In the end I always get this error:
Module not found: Error: Can't resolve 'fs' in 'C:\Users\myUsername\source\repos\electronTest\node_modules\electron'
I also tried nodeIntegration: true and contextIsolation: false

Just spent a while on this and <rant about JS community>... eh. Maybe this will help others.
tl;dr
You can only use electron's imports in electron-main side.
longer story
Electron is split into electron-main and electron-renderer. As others suggested, you need to update webpack's config with the target pointing at the correct electron.
In case you're using one of the electron builder boilerplates, you might see directory .erb with a few webpack configs. Especially, there might be one with target = "electron-main" and another with target = ["web", "electron-renderer"]. So it feels like mission accomplished; however, according to webpack's doc on target, if you pass a list, then a common config is set. Since web doesn't include fs, the common part also won't include fs.
For the reason above, some of electron imports, e.g. clipboard, can be only used from the "electron-main" side of your application.
The work-around, e.g. using clipboard on the application side, is to use IPC to communicate between main and renderer sides.

Check your webpack.config.js. Looks like you target is not electron-main or electron-renderer.

Related

JavaScript/TypeScript Dynamic Imports and browsers - options

I have a relatively simple requirement to start out with.
I want to be able to load a particular UI component, based on some dynamic context. I was thinking that dynamic imports could be used for this purpose. The dynamic imports should be loaded through the Web browser (no nodeJS).
Details about what is possible with dynamic imports are very sketchy to me - at best, probably also because I'm not an expert in the field of JavaScript/TypeScript (yet).
I'm using vanilla JS at the moment and vitejs as build tool.
Here is what I have so far.
This is my main.ts file:
const getPath = () => {
if (import.meta.env.MODE === 'development') {
return 'http://127.0.0.1:3000/one.js';
} else {
return 'http://127.0.0.1:5000/one.js';
}
};
import(getPath()).then((Module) => {
Module.default();
});
This already makes vitejs barf, complaining that it cannot analyze things - but I'm ignoring that for now. The one.js file looks like this:
const hello = () => {
console.log('Hello from one');
};
export default hello;
Both run dev as run serve work with this setup, as-in, the message is printed on the browser console.
My next thing I want to see working is how one.js would be able to itself import a module and work with that. I've tried this with moment like so:
import moment from "moment";
const hello = () => {
console.log('Hello from one ', moment().format());
};
export default hello;
This errors with:
Uncaught (in promise) TypeError: Error resolving module specifier “moment”. Relative module specifiers must start with “./”, “../” or “/”.
Now I don't know whether what I want to do, is not support or that I'm just not doing it the right way. Any pointers would be appreciated.

How to add custom scripts bundle in NextJS

I have some legacy custom javascripts that I need to bundle and put them in _document.js as a link. The filename should include a hash.
What would be the best way to accomplish this?
I tried webpack configs regarding entry/output but they break NextJs build.
The problem is that we use things like window, document, etc that do crash in server side.
Ideally what is needed is to inject this into a tag, as compiled / babelified javascript code.
What I tried is
Webpack HTML Plugin plus other plugins like InlineChunk or
InlineSource plugins. They didn't work because they generate code in
an index.html that is not used by NextJS.
Using Raw Loader to get the file content. Doesn't work because it is
not babelified.
Adding a custom entry to the Webpack config, like scripts:
'path/to/my-entry.js'. Didn't work because it adds a hash name to the
file and I have no way of knowing it.
Adding a custom entry into the NextJs polyfills. I thought it made
sense, but the polyfill tag has a nomodule which prevents its code to
run on new browsers.
Another options is to add the javascript code as a string, and then using __dangerouslySetInnerHtml but the problem is that I lose linter and babel abilities there.
I tried adding it as a page, but crashes for local development and even on build
webpack.config.js
module.exports = (nextConfig = {}) =>
Object.assign({}, nextConfig, {
webpack(config, options) {
const nextJsEntries = config.entry;
config.entry = async () => {
const entries = await nextJsEntries();
entries['pages/rscripts'] = 'test/test.js';
return entries;
};
...
Then in _document.js
<script src={`${publicRuntimeConfig.ASSET_PREFIX}/_next/${this.props.buildManifest.pages['/rscripts'][2]}`} />
You can just import js file like import 'path/to/js_file' in your _app.js/app.tsx file
import "../styles/globals.css"
import "../js/test"
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
This one works fine for me
I wanted to add another answer here as I came across this and I believe some things have changed in Next JS. Next now has this script component that you can use to load external scripts or dangerously set a script.
The Next.js Script component, next/script, is an extension of the HTML
element. It enables developers to set the loading priority of
third-party scripts anywhere in their application, outside next/head,
saving developer time while improving loading performance.
The cool thing is you can put them into whatever pages you want, maybe you have a script you want on a homepage, but not other pages, and Next will extract them and place them on the page based on the strategy you select. There are a few gotchas, can't load in the head, beforeInteractive is a little finicky, so I would read the link above and the actual API reference before making any choices.
import { useEffect } from 'react';
import Script from 'next/script';
function thirdPartyScript() {
useEffect(() => {
// just for fun. This actually fires
// before the onLoad callback
}, []);
return (
<Script
id="test-script"
strategy="afterInteractive"
src="/public/pages/scripts/test.js"
onLoad={() => {
console.log('Onload fires as you would expect');
}}
/>
);
}

Vue 3 component incorrectly initialized when module is `npm link`ed

Following is the entry point to my library, it generates a component with a dynamic tag:
// muvement.js
import { defineComponent, ref, onMounted, h } from 'vue';
const createMuvement = (tag) => {
return defineComponent({
name: `m-${tag}`,
setup(props, context) {
const root = ref(null);
onMounted(() => {
console.log(root.value);
});
return () => h(tag, { ...context.attrs, ref: root }, context.slots);
}
});
};
const muvement = (...tags) => {
const components = {};
tags.map((tag) => (components[`m-${tag}`] = createMuvement(tag)));
return components;
};
export { muvement };
It's expected to be consumed like so:
// Home.vue
<template>
<div>
<m-div>div</m-div>
<m-button>button</m-button>
</div>
</template>
<script>
import { muvement } from "muvement";
export default {
name: "Home",
components: {
...muvement("div", "button")
}
};
</script>
This works as expected when the library code is contained within the Vue app folder (assuming we are now importing from "#/components/muvement.js" instead of "movement").
That is:
-muvement-test-project (scaffolded with vue-cli)
- src
- views
- Home.vue
- components
- muvement.js
I've also published an alpha release that works fine when importing "muvement" after installing it directly from the npm registry (that is, npm install muvement instead of npm link muvement).
The Problem
During development, I want an app to test the library with that is separate from the library's directory.
I've used npm link to link the library to the test app (as I have done with many other projects in the past).
From /path/to/library
$ npm link
From /path/to/test/app
$ npm link muvement
So far so good. The module is available as a symlink in the test app's node_modules folder. So I import { muvement } from "muvement", run npm run serve, and... BOOM.
Everything explodes (see errors below). It's also probably worth noting that trying to import from the full path (i.e. C:/dev/npm/muvment/dist/es/index.js) results in the same issues as npm link does, so I don't think it has anything to do with the symlink directly.
This is what appears in the console:
For pretty much the entire day I have been trying to solve this one issue. I've seen several seemingly similar questions that were solved by settings Webpack's resolve.symlinks to false but that has no effect on my problem. I've read all through the docs and even Vue's source code (here is the offending line for those who are curious).
Since the warning suggests that the error is commonly attributed to async setup I thought maybe webpack was doing something weird that would make my code async. This doesn't seem to be the case as the call stack of both the working attempt and failed attempt are identical.
What's not identical is the scope.
Here is the scope for the example that is working:
And here is the failing one:
(Notice that the target parameter is null during the call to injectHook, which is obviously what prompts Vue to show a warning).
My question is, why does the location of the imported module make such a difference during the execution of the said module?
The library code and build setup are available here:
https://github.com/justintaddei/muvement
The test app is available here:
https://github.com/justintaddei/muvement/tree/example
If I've left out something important, please let me know in the comments. It's been a long day so I'm sure I've probably missed something.
Thank you.
The problem is your app is using two different vue dependencies under the hood - vue requires the same dependency to be used to keep track on reactivity, lifecycle, etc.
When you link a library npm/yarn will use that linked folder node_modules, but your app is using it's dependencies from it's node_modules.
When your app imports vue it will go app/node_modules/vue but when you import from your linked dependency it will be going to linked_dep/node_modules/vue.
app
node_modules
vue
linked library
node_modules
vue
One easy way to debug this issue is to change both vue dependency files with a console.log and check if the console is logging both.

How to import a module from the static using dynamic import of es6?

I'm trying to add dynamic import into my code to have a better performance on the client-side. So I have a webpack config where is bundling js files. On SFCC the bundled files are in the static folder where the path to that files is something like this: /en/v1569517927607/js/app.js)
I have a function where I'm using dynamic import of es6 to call a module when the user clicks on a button. The problem is that when we call for that module, the browser doesn't find it because the path is wrong.
/en/lazyLoad.js net::ERR_ABORTED 404 (Not Found)
This is normal because the file is on /en/v1569517927607/js/lazyLoad.js.
There is a way to get it from the right path? Here is my code.
window.onload = () => {
const lazyAlertBtn = document.querySelector("#lazyLoad");
lazyAlertBtn.addEventListener("click", () => {
import(/* webpackChunkName: "lazyLoad" */ '../modules/lazyLoad').then(module => {
module.lazyLoad();
});
});
};
I had the same problem and solved it using the Merchant Tools > SEO > Dynamic Mapping module in Business Manager.
There you can use a rule like the following to redirect the request to the static folder:
**/*.bundle.js i s,,,,,/js/{0}.bundle.js
All my chunk files are named with the <module>.bundle pattern.
Here you can find more info :
https://documentation.b2c.commercecloud.salesforce.com/DOC1/topic/com.demandware.dochelp/content/b2c_commerce/topics/search_engine_optimization/b2c_dynamic_mappings.html
Hope this helps.
I believe you'll likely need to do some path.resolve() magic in either your import statement or your webpack.config.js file as is shown in the accepted answer to this question: Set correct path to lazy-load component using Webpack - ES6
We did it in a different way. That required two steps
From within the template file add a script tag that creates a global variable for the static path. Something like
// inside .isml template
<script>
// help webpack know about the path of js scripts -> used for lazy loading
window.__staticPath__ = "${URLUtils.httpsStatic('/')}";
</script>
Then you need to instruct webpack to know where to find chunks by changing __webpack_public_path__ at runtime
// somewhere in your main .js file
// eslint-disable-next-line
__webpack_public_path__ = window.__staticPath__ + 'js/';
Optional step:
You might also want to remove code version from your __staticPath__ using replace (at least we had to do that)
__webpack_public_path__ = window.__staticPath__.replace('{YOUR_CODE_VERSION_GOES_HERE}', '') + 'js/';

Conditionally import assets in create-react-app

Is it possible to conditionally import assets when creating a React app using create-react-app? I'm aware of the require syntax - example:
import React from "react";
const path = process.env.REACT_APP_TYPE === "app_1" ? "app_1" : "app_2";
const imagePath = require(`./assets/${path}/main.png`);
export default function Test() {
return (
<img src={imagePath} alt="" />
);
}
This however bundles all my assets no matter what.
It will load the proper image, but it will still bundle all the files together in the final build.
When I look in the dev tools for the final build, I can see all the assets there even though I only wanted to load the assets for app_1.
Am I forced to touch the webpack config, if so, what should I change? or is there another way?
In the days when React didn't exist we didn't put assets into our JS files. We let the CSS to decide, what assets to load for what selectors. Then you could simply switch a corresponding class on or off for a corresponding element (or even the whole page) and viola it changes color, background, or even a form. Pure magic!
Ah. What times these were!
All above is true and I do not understand why would anyone do or recommend doing it differently. However if you still want to do it (for any reason) - you can! Latest create-react-app comes with out-of-the-box support for lazy loading of arbitrary components via dynamic importing and code splitting. All you need to do is use parenthesized version of the import() statement instead of the regular one. import() takes in a request string as usual and returns a Promise. That's it. Source code of the dynamicaly requested component won't be bundled in, but instead stored in separate chunks to be loaded on demand.
Before:
import OtherComponent from './OtherComponent';
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
After:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
Notice how function MyComponent part is identical.
For those wondering if it is tied to CRA or React, it's not. It's a generic concept that can be used in vanilla JavaScript.
You will need to use webpack (or other bundler.) The code is not being run when it's bundled, so the compiler has no way of knowing which branch of logic to follow (app_1 or app_2). Therefore you have to get into the bundler's logic in order to achieve your goal.
However, this isn't as scary as it seems since webpack has built in capability to do this (no 3rd parties required...)
I would look into using webpack.providePlugin
(https://webpack.js.org/plugins/provide-plugin)
or its sibling DefinePlugin
(https://webpack.js.org/plugins/define-plugin)
(I'm afraid these examples are off the top of my head, so it's very unlikely they'll work on first pass.)
Examples:
Both will require a provider module...
// in path/provider.js
module.exports = {
live: '/path/to/live/image',
dev: '/path/to/dev/image'
}
Provide Plugin Example
// in webpack
new webpack.ProvidePlugin({
imagePath: [
'path/provider', // the file defined above
process.env.ENVIRONMENT // either 'dev' or 'live'
]
}),
// in code
export default function Test() {
return (
<img src={imagePath} alt="" />
);
}
Define Plugin example:
// in webpack
new webpack.DefinePlugin({
'process.env.ENVIRONMENT': JSON.stringify(process.env.ENVIRONMENT)
});
// in code
var providers = require('path/provider'); // same path provider as above
export default function Test() {
return (
<img src={providers[process.env.ENVIRONMENT]} alt="" />
);
}
In both cases the bundler is forced to collapse your variable to an actual literal value at compile time - before bundling has taken place. Since you have now collapsed the logical path down to a single option, it is now free to only bundle the relevant assets.
You can't do this with default CRA settings.
Because if your dynamic require or dynamic import path is not static, webpack won't be able to determine which assets to include in the final build folder, therefore, it will grab everything from your ./src folder, and put them all to your build folder.
There is a way to do it with default CRA settings
You can add to .env something like
REACT_APP_SKIN=1
Put your skin assets in public/css1, public/css2 etc. And include them in public/index.html using code like
<link href="/css%REACT_APP_SKIN%/theme.css" rel="stylesheet">

Categories

Resources