I've made a small Electron app which needs to load a preload js file.
When I start the app with electron ., it finds the file, but when the app is packaged, it doesn't.
The call is made here:
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
nativeWindowOpen: true,
webSecurity: false,
preload: path.join(__dirname, 'preload.js')
}
})
My simplified package.json:
"name": "app",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron .",
"build": "electron-packager . --platform=win32 --arch=x64 --overwrite"
}
"devDependencies": {
"electron": "^1.8.4",
"electron-packager": "^12.0.1",
}
My project structure:
- node_modules
- main.js
- preload.js
- package.json
I've checked the result of the path.join and in both cases, the path is correct, and the file is there.
For peoples using Electron Forge webpack typescript boilerplate :
Add the preload key in package.json file:
{
"config": {
"forge": {
"plugins": [
[
"#electron-forge/plugin-webpack",
{
"mainConfig": "./webpack.main.config.js",
"renderer": {
"config": "./webpack.renderer.config.js",
"entryPoints": [
{
"html": "./src/index.html",
"js": "./src/renderer.tsx",
"name": "main_window",
"preload": {
"js": "./src/preload.ts"
}
}
]
}
}
]
]
}
}
}
Preload script can be a typescript file.
Add MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY constant as value for preload:
// Tell typescript about this magic constant
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: any;
// [...]
const mainWindow = new BrowserWindow({
height: 1000,
width: 1500,
webPreferences: {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
}
});
In preload.ts write :
import {
contextBridge,
ipcRenderer
} from 'electron';
contextBridge.exposeInMainWorld(
'electron',
{
doThing: () => ipcRenderer.send('do-a-thing')
}
)
Add index.d.ts file, write :
declare global {
interface Window {
electron: {
doThing(): void;
}
}
}
Start your app, in your dev console you can type electron and view it is defined.
BONUS: getting the typing right for the contextBridge exposed API:
Why a separated fie ? Not sure if needed, but I prefer not having to import an interface from a file that contain main process code (like preload.ts) in renderer process.
// exposed-main-api.model.ts
export interface ExposedMainAPI {
doThat(data: string): Promise<number>;
}
// index.d.ts
declare global {
interface Window {
electron: ExposedMainAPI
}
}
// preload.ts
import {
contextBridge,
ipcRenderer
} from 'electron';
const exposedAPI: ExposedAPI = {
// You are free to omit parameters typing and return type if you feel so.
// TS know the function type thanks to exposedAPI typing.
doThat: (data) => ipcRenderer.invoke('do-that-and-return-promise', data)
};
// note: this assume you have a `ipcMain.handle('do-thing-and-return-promise', ...)`
// somewhere that return a number.
contextBridge.exposeInMainWorld('electron', exposedAPI);
Credits:
#deadcoder0904 that gave us the solution in an comment to a previous answer, here is the github issue he link : https://github.com/electron-userland/electron-forge/issues/1590
index.d.ts typing and small example usage of contextBridge: https://github.com/electron/electron/issues/9920#issuecomment-743803249
See also:
Explanations about security and also an example of using IPC: https://github.com/electron/electron/issues/9920#issuecomment-575839738
Preload script needs to be specified as an absolute path. Thus it will differ from the time you're running it in development versus running it packaged as an asar file.
const getSourceDirectory = () => isDev()
? path.join(process.cwd(), 'build', 'src') // or wherever your local build is compiled
: path.join(process.resourcesPath, 'app', 'src'); // asar location
const preload = path.resolve(getSourceDirectory(), 'preload.js');
Related
I am new to coding. And trying to make an electron app with react. In the app I want to save user login information in the app so that I can automatically fetch the data when the app launches. So, I am using electron-settings to save the data.
code sample:
app.jsx
...
import setting from "electron-settings";
function App() {
...
useEffect(() => {
const getUser = async () => {
return await setting.get('xpass-user')
}
console.log(getUser());
}, [])
return ...;
}
export default App;
electron.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const url = require('url')
const isDev = require('electron-is-dev')
function createWindow() {
// Create the browser window.
const win = new BrowserWindow({
width: 1250,
height: 900,
titleBarStyle: "hiddenInset",
autoHideMenuBar: true,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
}
})
win.loadURL(
isDev
? 'http://localhost:3000'
: url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
})
)
win.webContents.openDevTools();
}
app.on('ready',createWindow)
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
the error:
ERROR in ./node_modules/electron-settings/dist/settings.js 166:27-40
Module not found: Error: Can't resolve 'fs' in
'C:\Users\learner\app\node_modules\electron-settings\dist'
ERROR in ./node_modules/electron-settings/dist/settings.js 170:29-44
Module not found: Error: Can't resolve 'path' in
'C:\Users\learner\app\node_modules\electron-settings\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 }
ERROR in
./node_modules/electron-settings/node_modules/mkdirp/lib/find-made.js
3:4-19
Module not found: Error: Can't resolve 'path' in
'C:\Users\app\node_modules\electron-settings\node_modules\mkdirp\lib'
If anyone can help me I'd be grateful. Thank You
Module not found: Error: Can't resolve 'fs' in...
In create-react-app, they have stubbed out 'fs'.You cannot import it. They did this because fs is a node core module.
Add the following to the root of the "package.json" file.
"browser": {
"fs": false,
"path": false,
"os": false
}
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.
to resolve this issue check this question How to Polyfill node core modules in webpack 5
I have just finished working on a test project in electron. The problem is that when I package the app using an electron-packager into an executable, my main.html file does not load up in the window.
However, when I run with npm start it works absolutely fine. I have checked if there is any mistake in the path to my files but that's absolutely fine as well.
Here's my package.json file -
{
"name": "test",
"version": "1.0.0",
"description": "Just a test app",
"main": "src/main.js",
"scripts": {
"start": "electron ."
},
"author": "Ray",
"license": "MIT",
"devDependencies": {
"electron": "^9.0.5"
},
"dependencies": {
"electron-packager": "^15.0.0"
}
}
Here is my main.js
const { app, BrowserWindow } = require('electron')
function createWindow () {
// Create the browser window.
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
// and load the index.html of the app.
win.loadFile('./app/main.html')
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(createWindow)
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
Any help will be appreciated. Thanks.
First of all electron-packagershould be part of devDependencies, not dependencies.
Second: How do you pack? If you use an asar File you need to look at the Paths. Those change when packing. So I'd recommend you use something like
win.loadURL(`file://${__dirname}/main.html`)
or
win.loadURL(url.format({
pathname: path.join(__dirname, 'main.html'),
protocol: 'file:',
slashes: true
}));
when loading your html.
I'm trying to create my own webpack configuration for a react / redux project. The configuration seems to work pretty well, but the bundle size is big (in development mode, I know how to reduce it in production mode)
My package.json looks like
"dependencies": {
"react": "15.4.2",
"react-dom": "15.4.2",
"react-hot-loader": "3.0.0-beta.6"
},
"devDependencies": {
"babel-core": "6.24.0",
"babel-loader": "6.4.1",
"babel-preset-react": "6.23.0",
"commitizen": "2.9.6",
"cz-conventional-changelog": "2.0.0",
"html-webpack-plugin": "2.28.0",
"webpack": "2.3.1",
"webpack-bundle-analyzer": "^2.3.1",
"webpack-dev-server": "2.4.2"
}
and my webpack configuration looks like
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const HtmlWebpackPlugin = require('html-webpack-plugin');
const project = require('./project.config')
const __DEV__ = project.globals.__DEV__;
const __PROD__ = project.globals.__PROD__;
const __TEST__ = project.globals.__TEST__;
const APP_ENTRIES = [project.paths.client('index.js')];
if (__DEV__) {
APP_ENTRIES.unshift(
'react-hot-loader/patch',
`webpack-dev-server/client?http://${project.server_host}:${project.server_port}`,
'webpack/hot/only-dev-server'
)
}
const config = {
devtool: project.compiler_devtool,
entry: APP_ENTRIES,
output: {
path: project.paths.dist(),
filename: `[name].[${project.compiler_hash_type}].js`,
publicPath: project.compiler_public_path,
},
resolve: {
modules: [
project.paths.client(),
'node_modules',
],
},
module: {
rules: [{
test: /\.js?$/,
loader: 'babel-loader',
exclude: /node_modules/,
}],
},
plugins: [
new BundleAnalyzerPlugin(),
new HtmlWebpackPlugin({
template: project.paths.client('index.html'),
hash: false,
filename: 'index.html',
inject: 'body',
}),
],
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
}
}
if (__DEV__) {
config.plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin());
}
module.exports = config
Right now I got this output when I run webpack-dev-server
As you can see my bundle is bigger than 1.3 Mb but I'm only using a few libraries.
I tried to find out why is my bundle so big using webpack-bundle-analyser. Here is the result
It seems that react and readct-dom are the biggest libraries.
Is there a way to reduce the size of my bundle in development mode ??? Did I do something wrong ?
PS : I set compress = true in webpack-dev-server options, but it did not reduce the size of the bundle.
Here are more details about how I use webpack-dev-server
bin/start.js (I run it using node bin/start.js)
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const config = require('../config/webpack.config');
const project = require('../config/project.config');
function runDevServer() {
const devServer = new WebpackDevServer(webpack(config), {
compress: true,
hot: true,
publicPath: project.compiler_public_path,
stats: project.compiler_stats,
watchOptions: {
ignored: /node_modules/
},
});
// Launch WebpackDevServer.
devServer.listen(project.server_port, project.server_host, (err) => {
if (err) {
console.log('Webpack dev server encountered an error', err);
return reject(err);
}
console.log(`Listening at ${project.server_host}:${project.server_port}`);
});
}
runDevServer()
src/index.js (my application. Home is just a component returning "Hello world")
import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import Home from './routes';
const render = () => {
try {
ReactDOM.render(
<AppContainer>
<Home />
</AppContainer>,
document.getElementById('root')
);
} catch (err) {
console.error(err)
}
};
if (module.hot) {
module.hot.accept('./routes', () => render());
}
render()
Having a large bundle during development is normal, 1.3MB is still small and should normally be fast to load. Since you are using webpack-dev-server, it should take no time to reload after each change. If you use compress = true, you bundle size will be the same, except that it will be gzip compressed when the client ask for it (you should see gzip in the network tab). I will recommend you not to try to reduce the size of the bundle for development because it will be hard to debug and slower to refresh. IF you really want to reduce the size, you can uglify and minify only the libraries code. This is a very common approach, you will basically have every library in a file named vendor.js that you will minify, uglify, etc. In another file, bundle.js you will have all the code you've wrote. This will reduce significantly the total size. but you will have to load 2 js files. You can also active the tree shaking for webpack 2. Her are more info about the tree shaking. You can refer to this answer on how to bundle vendor script separately and read more about code splitting here. But keep in mind that you dev bundle will always be of a bigger size because it is very handy when debugging.
I'm converting a javascript project with Angular 1.x to WebPack and TypeScript (using ts-loader). I got it mostly working, but I'm running into trouble when ts-loader seems to be optimizing my scripts out of the bundle when the exports are not directly used.
Here's a sample project demonstrating the issue (npm install, webpack, then load index.html and watch the console).
https://github.com/bbottema/webpack-typescript
The logging from ClassA is showing up, but angular is reporting ClassB missing (provider). If you look in bundle.js you'll notice ClassB missing entirely. The difference is ClassA begin use directly after importing, and ClassB is only referenced by type for compilation.
Is it a bug, or is there a way to force ClassB to be included? Or am I going about it wrong? Angular 2 would probably solve this issue, but that's a step too large right now.
Relevant scripts from the project above:
package.json
{
"devDependencies": {
"typescript": "^1.7.5",
"ts-loader": "^0.8.1"
},
"dependencies": {
"angular": "1.4.9"
}
}
webpack.config.js
var path = require('path');
module.exports = {
entry: {
app: './src/entry.ts'
},
output: {
filename: './dist/bundle.js'
},
resolve: {
root: [
path.resolve('./src/my_modules'),
path.resolve('node_modules')
],
extensions: ['', '.ts', '.js']
},
module: {
loaders: [{
test: /\.tsx?$/,
loader: 'ts-loader'
}]
}
};
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs"
},
"exclude": [
"node_modules"
]
}
index.html
<!doctype html>
<html ng-app="myApp">
<body>
<script src="dist/bundle.js"></script>
</body>
</html>
entry.js
declare var require: any;
'use strict';
import ClassA = require('ClassA');
import ClassB = require('ClassB');
var a:ClassA = new ClassA(); // direct use, this works
var angular = require('angular');
angular.module('myApp', []).
// this compiles as it should, but in runtime the provider will not be packaged and angular will throw an error
run(function(myProvider: ClassB) {
}
);
ClassA.ts
// this line will be logged just fine
console.log('ClassA.ts: if you see this, then ClassA.ts was packaged properly');
class ClassA {
}
export = ClassA;
ClassB.ts
declare var require: any;
// this line is never logged
console.log('ClassB.ts: if you see this, then ClassB.ts was packaged properly');
class ClassB {
}
var angular = require(angular);
angular.module('myApp').service(new ClassB());
export = ClassB;
Turns out you have to signal WebPack to explicitly include a module by adding an extra require call without import statement.
I'm not ready to mangle my .ts files by adding duplicate imports, so I made a generic solution for that using the preprocessor loader:
{
"line": false,
"file": true,
"callbacks": {
"fileName": "all",
"scope": "line",
"callback": "(function fixTs(line, fileName, lineNumber) { return line.replace(/^(import.*(require\\(.*?\\)))/g, '$2;$1'); })"
}]
}
As a proof of concept, this regex version is very limited it only support the following format:
import ClassA = require('ClassA');
// becomes
require('ClassA');import ClassA = require('ClassA');
But it works for me. Similarly, I'm adding the require shim:
{
"fileName": "all",
"scope": "source",
"callback": "(function fixTs(source, fileName) { return 'declare var require: any;' + source; })"
}
I made a sample project with this solution.
I want to build a quick nodejs script to package a Typescript app as SystemJS modules, a lot like what Angular2 bundles look like.
I tried different configurations but I can't seem to put my finger on it, and haven't found clear enough documentation as of yet.
Note that for this "test", I am not using Gulp or Jspm at all, just systemjs-builder for the time being (and don't plan on using jspm at all either)
Here's what my "project" looks like:
---- Project's Root
-------- index.ts // export * from './modules/index' and eventually more
-------- modules
------------ index.ts // export * from './menu/index'
------------ menu
---------------- menu.component.ts // export class
---------------- menu.service.ts // export class
I want to package this under a single file, where I will have multiple SystemRegister modules that can be consumed in an app thereafter
I tried the following without success:
var Builder = require('systemjs-builder');
// optional constructor options
// sets the baseURL and loads the configuration file
var builder = new Builder('./modules');
builder.bundle('./modules/index.ts', {
/* SystemJS Configuration Here */
baseURL: './modules',
transpiler: 'typescript',
typescriptOptions: {
"module": "system",
"emitDecoratorMetadata": true,
"experimentalDecorators": true
},
defaultExtension: 'ts',
packages: {
'modules': {
defaultExtension: 'ts'
}
}
}, 'infrastructure.js')
.then(function() {
console.log('Build complete');
})
.catch(function(err) {
console.error(err);
})
First of all, the defaultExtension options doesn't seem to work at all
So when I do import {something} from 'filePath'; (without extension), it tries to load filePath, instead of filePath.ts;
Second, if I try adding the .ts extension in my imports (which I don't want to do), it complains that the code is invalid (unexpected token #, unexpected token menuItem and so forth)
Anyone have a good example or some explanations on how this is supposed to work?
Thank you
here you have an example: angular typescript skeleton
build task looks like this:
const path = require('path');
const Builder = require('jspm').Builder;
const builder = new Builder();
const packageJson = require(path.join(config.projectDir, 'package.json'));
return beginBuild()
.then(buildSFX)
.catch((err) => console.log('Build Failed', err));
function beginBuild() {
builder.reset();
return builder.loadConfig(path.join(config.projectDir, packageJson.jspm.configFile))
}
function buildSFX() {
const appName = packageJson.name;
const distFileName = `${appName}.min.js`;
const outFile = path.join(config.distDir, distFileName);
const moduleName = 'app';
const buildConfig = {
format: 'global',
minify: true,
sourceMaps: true
};
return builder.buildStatic(moduleName, outFile, buildConfig);
}
and jspm conf looks like this:
System.config({
defaultJSExtensions: true,
transpiler: "typescript",
typescriptOptions: {
"tsconfig": "src/tsconfig.json"
},
paths: {
"github:*": "vendor/jspm_packages/github/*",
"npm:*": "vendor/jspm_packages/npm/*",
"app": "src/index"
}
/// ...
}
Why do you want to bundle typescript? Bundling is a method used for optimizing the delivery of source code to the browser. The browser doesn't know typescript, it only knows javascript (unless you do on the fly transpiling).