Packaging Keytar with an Electron app - node.js

I'm using electron-builder (16.6.2) to package my electron application which includes keytar (3.0.2) as a prod dependency.
package.json file includes:
"scripts": {
"postinstall": "install-app-deps",
"compile:dev": "webpack-dev-server --hot --host 0.0.0.0 --config=./webpack.dev.config.js",
"compile": "webpack --config webpack.build.config.js",
"dist": "yarn compile && build"
},
"build": {
"appId": "com.myproject",
"asar": true,
"files": [
"bin",
"node_modules",
"main.js"
]
}
When I run the .app on the same system it runs fine. When I try running it on a different system (or deleting my node_modules) it fails to find keytar.node. When keytar is built, it includes a fully qualified path to that image for my system. I get the following error in the console:
Uncaught Error: Cannot open /Users/Kevin/Work/myproject/node_modules/keytar/build/Release/keytar.node
Error: dlopen(/Users/Kevin/Work/myproject/node_modules/keytar/build/Release/keytar.node,
1): image not found
I must be missing a step in the build process.

As it turns out, I was using keytar in the renderer process. I moved keytar into the main process (which doesn't go through Webpack / Babel) and gets packed correctly by electron-builder.
main.js
ipcMain.on('get-password', (event, user) => {
event.returnValue = keytar.getPassword('ServiceName', user);
});
ipcMain.on('set-password', (event, user, pass) => {
event.returnValue = keytar.replacePassword('ServiceName', user, pass);
});
Then from the renderer process I can call
const password = ipcRenderer.sendSync('get-password', user);
or
ipcRenderer.sendSync('set-password', user, pass);

window.require("electron").remote.require("keytar")
Since you are working on renderer process and want to use native api from system or main process.

Update:
I found (as per the OP) that transpiling my main thread code (which uses keytar) resulted in calls to keytar functions returning TypeError: keytar.findPassword is not a function.
I had to use webpack-asset-relocator-loader to bundle keytar successfully:
npm i -DE #vercel/webpack-asset-relocator-loader
Add the following rule to your webpack.config.js:
module: {
rules: [{
test: /\.node$/,
parser: { amd: false },
use: {
loader: "#vercel/webpack-asset-relocator-loader",
options: {
outputAssetBase: "native_modules"
}
}
},
// <other rules>
],
// rest of config
}
Solution found in this Github issue.
The information below still stands for including binary assets in your webpack build.
If you have to transpile code that requires a binary file, you can add file-loader to your webpack config.
Install
npm i -D file-loader
or
yarn add -D file-loader
webpack config (to include a .dat file)
...,
module: {
rules: [{
...
}, {
test: /\.dat$/,
use: {
loader: "file-loader"
}
}]
},
...
If you want to preserve the filename, you can pass name options to the loader:
use: {
loader: "file-loader",
options: {
name: "[name].[ext]"
}
}
More information on the file-loader Github repo.

Related

Bundle NPM Package so it has different import paths with Vite and Typescript

How can I bundle my NPM package in a way that I can have different import paths for different parts of the package? I have found webpack approaches, but I am using Vite and TS.
My package looks like this:
- src
- atoms
- molecules
- organism
- index.ts (currently simply imports and exports everything)
Now I can use this currently like this
import { Button } from '#mypackage/library'
How can I do it, so I get this outcome:
import { Button } from '#mypackage/library/atom'
Here is the relevant part of my package.json
{
"entry": "src/index.ts",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"src"
],
"exports": {
".": {
"import": "./dist/index.es.js",
"require": "./dist/index.cjs.js",
"types": "./dist/index.d.ts"
},
"./package.json": "./package.json",
"./atoms": "./src/atoms/index.ts",
"./molecules": "./src/molecules/index.ts",
"./organisms": "./src/organisms/index.ts",
"./theme": "./src/theme/index.ts"
},
}
Here is my vite.config.ts
export default defineConfig({
build: {
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
formats: ['es', 'cjs'],
name: '#workdigtital/component-library-react',
fileName: (format) => `index.${format}.js`
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
},
exports: 'named'
}
}
},
plugins: [react(), dts({ insertTypesEntry: true })],
resolve: {
alias: {
'#': path.resolve(__dirname, './src')
}
}
});
If I currently try an import like this, inside another project (Laravel+React), in which installed the library.
import { ThemeProvider } from '#workdigital/component-library-react/theme';
I get the following run time error (But no Typescript errors, even IntelliSense is working):
Failed to load url /resources/js/theme/ThemeProvider (resolved id: /resources/js/theme/ThemeProvider). Does the file exist?
My resulting Dist folder looks like this:
You can't have TypeScript exports, this simply won't work. An npm package should have only JS exports.
If you want to be able to selectively import different parts of your package, you must transpile them to different files.
rollup can do it, but it is lots of work, as you will have to set up a separate target for each exported file. Normally you use rollup to create a single bundle, this what this tool is made for.
tsc with a tsconfig.json will be a much better choice in your case. It does this by default, you only need to specify the output directory and it will produce a separate file for each source.
There is an excellent guide on the TypeScript site about packaging TypeScript libraries, you should probably start there.

How to configure Vite to output single bundles for a Chrome DevTools Extension?

I am trying to create a Chrome DevTools Extension with Vite.
There are a couple different entry points. The main two are src/background.ts and devtools.html (which references src/devtools.ts).
There are is some code that I want to shared between them in src/devtools-shared.ts.
After running the build, the entry points still contain import statements. Why and how do I get rid of them so they are self-contained bundles (Ideally not IIFE, just good old top level scripts)?
Here is what I have got:
vite.config.js:
const { resolve } = require('path')
const { defineConfig } = require('vite')
module.exports = defineConfig({
resolve: {
alias: {
"root": resolve(__dirname),
"#": resolve(__dirname, "src")
}
},
esbuild: {
keepNames: true
},
build: {
rollupOptions: {
input: {
'background': "src/background.ts",
'content-script': "src/content-script.ts",
'devtools': "devtools.html",
'panel': "panel.html",
},
output: {
entryFileNames: chunkInfo => {
return `${chunkInfo.name}.js`
}
},
// No tree-shaking otherwise it removes functions from Content Scripts.
treeshake: false
},
// TODO: How do we configured ESBuild to keep functions?
minify: false
}
})
src/devtools-shared.ts:
export const name = 'devtools'
export interface Message {
tabId: number
}
src/background.ts:
import * as DevTools from './devtools-shared'
src/devtools.ts:
import * as DevTools from './devtools-shared'
And then in dist/background.js I still have:
import { n as name } from "./assets/devtools-shared.8a602051.js";
I have no idea what controls this. I thought there would not be any import statements.
Does the background.ts entry point need to be a lib or something?
For devtools.html is there some other option that controls this?
I know there is https://github.com/StarkShang/vite-plugin-chrome-extension but this doesn't work very well with Chrome DevTool Extensions. I prefer to configure Vite myself.
It turns out that this is not possible. Rollup enforces code-splitting when there are multiple entry-points. See https://github.com/rollup/rollup/issues/2756.
The only workaround that I can think of is to have multiple vite.config.js files such as:
vite.config.background.js
vite.config.content-script.js
vite.config.devtools.js
Then do something like this in package.json:
"scripts": {
"build": "npm-run-all clean build-background build-content-script build-devtools",
"build-background": "vite build -c vite.config.background.js",
"build-content-script": "vite build -c vite.config.content-script.js",
"build-devtools": "vite build -c vite.config.devtools.js",
"clean": "rm -rf dist"
},
This is not very efficient as it repeats a lot of work between each build but that's a Rollup problem.

BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default

I'm trying to use Socket.io in my Laravel/Vue.js app. But I'm getting this error when running npm run dev.
Module not found: Error: Can't resolve 'path' in '/home/nicolas/LaravelProjects/Chess/node_modules/socket.io/dist'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "path": require.resolve("path-browserify") }'
- install 'path-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "path": false }
webpack compiled with 9 errors
I tried changing the webpack.config.js file by adding resolve.fallback: { "path": false }, but it doesn't seem to help. It is possible that I'm doing it wrong since I have 4 webpack.config.js files in my project (I don't know why).
Maybe can I downgrade webpack? Thanks!
This fix worked for me (Vue 3):
In vue.config.js, add:
const { defineConfig } = require('#vue/cli-service')
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin")
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
plugins: [
new NodePolyfillPlugin()
]
}
})
Followed by npm install node-polyfill-webpack-plugin
I had this problem in ReactJS with create-react-app(facebook) but other packages (crypto-browserify)
Solution:
First install the necessary packages in this problem "path-browserify" but in my case "crypto-browserify"
Modify webpack.config.js in reactjs with create-react-app this file is inside:
node_modules/react-scripts/config/webpack.config.js
Search module.exports and inside this function there is a return:
module.exports = function (webpackEnv) {
...
return {
...
resolve: {
...
fallback: {
// Here paste
path: require.resolve("path-browserify"),
// But in mi case I paste
crypto: require.resolve("crypto-browserify"),
}
}
}
}
Note: This solution works, but when the webpack project starts it shows warnings
Pd: I am not native speaker English, but I hope understand me.
I have the same issue in React (feb-2022) using Real (realm-web) from Mongo.
I solve this in two steps:
npm i stream-browserify crypto-browserify
create fallback object into webpack.config.js at node_modules/react-scripts/config/webpack.config.js
module.exports = function (webpackEnv) {
...
return {
...
resolve: {
...
fallback: { // not present by default
"crypto": false,
"stream": false
}
}
}
I have the same issue with crypto. Some say that adding a proper path in TS config (and installing a polyfill) should resolve the issue.
tsconfig.json
{
"compilerOptions": {
"paths": {
"crypto": [
"./node_modules/crypto-browserify"
],
}
Details https://github.com/angular/angular-cli/issues/20819
I'm still fighting with crypto, but above link might help in your struggles.
I had the same issue, Strangely an import {script} from 'laravel-mix' in my app.js. I removed it and everything went back to normal.
I could not get the other answers to work with React on a new project with rss-parser. The error messages aren't clear. I fixed it by adding this line to the webpack configuration:
resolve: {
extensions: ['.tsx', '.ts', '.js'],
fallback: { "http": false, "browser": false, "https": false,
"stream": false, "url": false, "buffer": false, "timers": false
}
},
Watch out for this import in your app.js. Removing it fixed my issue.
use the version of webpack 4.46.0 to remove the error
I had to delete this line in a Vue component to solve the problem:
import { ContextReplacementPlugin } from "webpack";
If you use create-react-app(cra)
You have to overwrite the default webpack file
to do that you need to install a package like "react-app-rewired" that
will help overwriting and merge your configuration with the ones were
added by react scripts
First
npm install --D react-app-rewired
create a new file in the root directory called "config-overrides.js"
Install(using npm) and Add the required dependencies to the created config file
npm i -D path-browserify
install other packages as the error message shows
const webpack = require('webpack');
module.exports = function override(config) {
const fallback = config.resolve.fallback || {};
Object.assign(fallback, {
path: require.resolve('path-browserify'),
add others as shown in the error if there are more
});
config.resolve.fallback = fallback;
config.plugins = (config.plugins || []).concat([
new webpack.ProvidePlugin({
process: 'process/browser',
Buffer: ['buffer', 'Buffer'],
}),
]);
return config;
};
Update your package.json scripts
"scripts": {
"build": "react-app-rewired build",
....
"start": "react-app-rewired start",
"test": "react-app-rewired test"
},
Also please note that a new version of react-scripts(newer than 5.0.1) might solve the issue.
had this issue in a react-app.
installation of stream-browserify crypto-browserify didn't work for me
went through with hjpithadia's answer in Reactjs with a slight change in webpack.config.js
install the package
npm install node-polyfill-webpack-plugin
find the webpack configuration file here
node_modules/react-scripts/config/webpack.config.js
import the package
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
search for module.exports
in plugin array add this new NodePolyfillPlugin(),
module.exports = function (webpackEnv) {
...
return {
...
plugins: [
new NodePolyfillPlugin(),
...
]
}
}
For React apps, if you don't need the module make sure webpack ignores it.
install craco https://craco.js.org/docs/getting-started/
create a craco.config.js in the root of your project, mine looks like this:
module.exports = {
webpack: {
configure: (webpackConfig) => {
webpackConfig.resolve.fallback = webpackConfig.resolve.fallback || {}
webpackConfig.resolve.fallback.zlib = false
webpackConfig.resolve.fallback.http = false
webpackConfig.resolve.fallback.https = false
webpackConfig.resolve.fallback.stream = false
webpackConfig.resolve.fallback.util = false
webpackConfig.resolve.fallback.crypto = false
return webpackConfig;
},
}
};
I resolved similar issue:
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "stream": require.resolve("stream-browserify") }'
- install 'stream-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "stream": false }
Installing the npm i stream-browserify --save-dev
Making the custom webpack.config.js file containing:
module.exports = {
resolve: {
fallback: { stream: require.resolve("stream-browserify") },
},
};
Installing the npm install #angular-builders/custom-webpack --save-dev
Assigning (angular.json: projects -> <proj_name> -> architect -> build -> options)
"customWebpackConfig": {
"path": "./webpack.config.js"
}
Changing the: build / serve / extract-i18n / test – as of your needs – to use custom Webpack config:
"builder": "#angular-builders/custom-webpack:browser",
I finally found the solution. With laravel, you have to change the webpack.mix.js file and not webpack.config.js.

How to disable react-transform-hmr in production?

I have a problem with my create react app. When I run dev version everything is fine. When I'm trying to run build version I get error:
Uncaught Error: locals[0] does not appear to be a module object with Hot Module replacement API enabled. You should disable react-transform-hmr in production by using env section in Babel configuration. See the example in README: https://github.com/gaearon/react-transform-hmr
at n (react-datepicker.js:4634)
I tried this: babel.rc file
{
"presets": ["es2015", "stage-0"],
"env": {
// only enable it when process.env.NODE_ENV is 'development' or undefined
"development": {
"plugins": [[
"react-transform", {
"transforms": [{
"transform": "react-transform-hmr",
// if you use React Native, pass "react-native" instead:
"imports": ["react"],
// this is important for Webpack HMR:
"locals": ["module"]
}]
// note: you can put more transforms into array
// this is just one of them!
}
]]
}
}
}
and my build command is:
"build": "set NODE_ENV=production && react-scripts build",
Any idea why this happens or where should I look for solutions?
Thanks

Webpack/Express - environment variables not found by server

In my Express/React app, I am using Webpack to handle server-side rendering. However, I am experiencing a build error related to environment variables that I'm trying to access in my Express server script.
In the server script, index.js, I am setting a few variables like so:
const gitCommit = process.env.GIT_COMMIT || require("./gitignore/git_commit.js"),
buildDate = process.env.BUILD_DATE || require("./gitignore/build_date.js")
And since I am running a test production build on my local machine, I delete the gitignore/ directory and set those environment variables:
$ export GIT_COMMIT="test commit hash"
$ export BUILD_DATE="test build date"
Then I npm run build, which executes the following scripts:
"build:client": "webpack --config webpack.config.js",
"build:server": "webpack --config webpack.server.config.js",
"build": "npm run build:client && npm run build:server"
build:client executes with no problem, but build:server throws errors...
ERROR in ./index.js
Module not found: Error: Can't resolve './gitignore/git_commit.js' in '/Users/filepath'
# ./index.js 12:38-74
ERROR in ./index.js
Module not found: Error: Can't resolve './gitignore/build_date.js' in '/Users/filepath'
# ./index.js 13:42-78
implying that the two environment variables referenced in index.js can't be found, and so it's looking for the gitignore/ instead, which shouldn't exist (I mean, it does exist locally, but I've deleted since I'm simulating a production build).
Here is the complete webpack.server.config.js:
const fs = require("fs"),
path = require("path")// ,
// ExtractTextPlugin = require("extract-text-webpack-plugin")
module.exports = {
"entry": path.resolve(__dirname, "index.js"),
// keep node_module paths out of the bundle
"externals": fs.readdirSync(path.resolve(__dirname, "node_modules")).concat(["react-dom/server", "react/addons"]).reduce((ext, mod) => {
ext[mod] = `commonjs ${mod}`
return ext
}, {}),
"module": {
"loaders": [
{
"exclude": /node_modules/,
"loader": "babel-loader",
"query": { "presets": ["react", "es2015", "stage-2"] },
"test": /\.jsx$/
},
{
"exclude": /node_modules/,
"loader": "babel-loader",
"query": { "presets": ["react", "es2015", "stage-2"] },
"test": /\.js$/
}
]
},
"node": {
"__dirname": true,
"__filename": true
},
"output": {
"filename": "server.bundle.js"
},
"target": "node"
}
Now I expect that gitignore/ would not be found, but what I don't understand is why the two environment variables that I set are not being detected by index.js - they are definitely set in the console before I even run the build command. If I console.log() them in the beginning of webpack.server.config.js, it logs them correctly, and if I run my development version instead (which doesn't use the server config), I can log them correctly in index.js. What gives?
Node version 6.11.1, NPM version 3.10.10, Webpack version 2.6.0.
Your environment variables are only available when Webpack runs, but not when you execute your index.js.
You will need to use the EnvironmentPlugin in your Webpack config like that:
plugins: [new webpack.EnvironmentPlugin(['GIT_COMMIT ', 'BUILD_DATE'])]
That plugin will replace the variables by their actual values.
HINT: Do not use ||. Webpack does not know how to optimize it. Try the ternary operator:
const gitCommit = (process.env.GIT_COMMIT) ? (
process.env.GIT_COMMIT
) : (
require('./gitignore/git_commit.js')
);
Webpack will bundle this to:
const gitCommit = (true) ? (
"test commit hash"
) : (
require('./gitignore/git_commit.js')
);
No IgnorePlugin is needed. Even better, with the UglifyJSPlugin, your code will be optimized to const gitCommit = "test commit hash";. In some cases gitCommit is removed completely as a variable. Its string value will be used instead anywhere where you applied gitCommit.

Resources