Last active
October 20, 2022 18:50
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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