Skip to content

Instantly share code, notes, and snippets.

@Demindiro
Last active October 20, 2022 18:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Demindiro/da53154e032ba7c3be7e8d8054e07ac3 to your computer and use it in GitHub Desktop.
Save Demindiro/da53154e032ba7c3be7e8d8054e07ac3 to your computer and use it in GitHub Desktop.
There's quite a few posts explaining how to set up a React + Electron app but somehow I couldn't find a single-file script to just set up something.
#!/usr/bin/env bash
# https://mmazzarolo.com/blog/2021-08-12-building-an-electron-application-using-create-react-app/
set -ex
if [ $# != 1 ]
then
echo "Usage: $0 <path>" >&2
exit 1
fi
npx create-react-app "$1"
cd "$1"
yarn add -D concurrently cross-env electron electron-builder electronmon wait-on
cat << EOF > public/electron.js
// Module to control the application lifecycle and the native browser window.
const { app, BrowserWindow, protocol } = require("electron");
const path = require("path");
const url = require("url");
// Create the native browser window.
function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
// Set the path of an additional "preload" script that can be used to
// communicate between node-land and browser-land.
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
});
// In production, set the initial browser path to the local bundle generated
// by the Create React App build process.
// In development, set it to localhost to allow live/hot-reloading.
const appURL = app.isPackaged
? url.format({
pathname: path.join(__dirname, "index.html"),
protocol: "file:",
slashes: true,
})
: "http://localhost:3000";
mainWindow.loadURL(appURL);
// Automatically open Chrome's DevTools in development mode.
if (!app.isPackaged) {
mainWindow.webContents.openDevTools();
}
}
// Setup a local proxy to adjust the paths of requested files when loading
// them from the local production bundle (e.g.: local fonts, etc...).
function setupLocalFilesNormalizerProxy() {
protocol.registerHttpProtocol(
"file",
(request, callback) => {
const url = request.url.substr(8);
callback({ path: path.normalize(\`\${__dirname}/\${url}\`) });
},
(error) => {
if (error) console.error("Failed to register protocol");
}
);
}
// This method will be called when Electron has finished its initialization and
// is ready to create the browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow();
setupLocalFilesNormalizerProxy();
app.on("activate", function () {
// 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();
}
});
});
// 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", function () {
if (process.platform !== "darwin") {
app.quit();
}
});
// If your app has no need to navigate or only needs to navigate to known pages,
// it is a good idea to limit navigation outright to that known scope,
// disallowing any other kinds of navigation.
const allowedNavigationDestinations = "https://my-electron-app.com";
app.on("web-contents-created", (event, contents) => {
contents.on("will-navigate", (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl);
if (!allowedNavigationDestinations.includes(parsedUrl.origin)) {
event.preventDefault();
}
});
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
EOF
cat << EOF > public/preload.js
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
const { contextBridge } = require("electron");
// As an example, here we use the exposeInMainWorld API to expose the browsers
// and node versions to the main window.
// They'll be accessible at "window.versions".
process.once("loaded", () => {
contextBridge.exposeInMainWorld("versions", process.versions);
});
EOF
# TODO
node << EOF
const fs = require('node:fs');
const cfg = JSON.parse(fs.readFileSync('package.json'));
cfg.main = './public/electron.js';
cfg.homepage = './';
cfg.browserslist = {
production: [ 'last 1 electron version' ],
development: [ 'last 1 electron version' ],
};
cfg.scripts['electron:start'] = 'HOST=localhost concurrently -k "cross-env BROWSER=none yarn start" "wait-on http://localhost:3000 && electronmon ."';
const packager = 'yarn build && electron-builder -% -c.extraMetadata.main=build/electron.js';
cfg.scripts['electron:package:mac'] = packager.replace('%', 'm');
cfg.scripts['electron:package:win'] = packager.replace('%', 'w');
cfg.scripts['electron:package:linux'] = packager.replace('%', 'l');
cfg.build = {
appId: 'com.example.appname',
productName: 'React + Electron App',
files: ['build/**/*', 'node_modules/**/*'],
directories: { buildResources: 'public' },
mac: { target: 'dmg' },
win: { target: 'nsis' },
linux: { target: 'deb' },
};
fs.writeFileSync('package.json', JSON.stringify(cfg, undefined, 2));
EOF
set +x
echo 'Add the following line to public/index.html'
echo '<meta http-equiv="Content-Security-Policy" content="script-src '\''self'\'' '\''unsafe-inline'\'';">'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment