Skip to content

Commit

Permalink
migrate to esbuild (#86)
Browse files Browse the repository at this point in the history
- migrate build from webpack to esbuild (closes #65)
- delete all webpack dependencies
- load preload script as a preload script
- enable numerous security options
- re-work build and dev scripts (use esbuild, watch together, etc)
  • Loading branch information
cloverich authored Oct 21, 2021
1 parent da489df commit acd5944
Show file tree
Hide file tree
Showing 25 changed files with 988 additions and 4,756 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ tmp/
# build output
dist/
packaged/

# bundler (dev) outputs .bundle files
# along-side src files, used during development
*.bundle.*
29 changes: 16 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,31 @@
**Status**: Hobby project, in development. Prototyping hacks and ideas -- bugs abound. Will post usable(ish) versions as releases when it gets to stopping points.


## Migrating the database
:|

### Database file
The SQLite database file is specified in the [settings.json](https://github.com/nathanbuchar/electron-settings) file. It can be configured in preferences.

## Development
The app is a typical Electron dev setup, but serves the UI from webpack dev server while in development. To start the app you'll need to start both the webpack dev server and electron.
The app is a typical Electron dev setup, excepting the use of [esbuild][1].

```bash
# install dependencies
yarn

# start webpack
yarn dev

# start electron
yarn dev:electron
# start the development build and watch script
yarn start
```

## Build and release
See scripts/dev.js for specifics on how the source files are compiled and re-loaded in development. Settings

- Use the `build.sh` script
## Build and release
- Read and use the `build.sh` script
- Make a Github release

At a high level, the build is comprised of:
- generate bundles ([esbuild][1]) from source files
- install production dependencies
- [re-build][2] native dependencies for the targeted electron version
- [package][3] the app


[1]: https://esbuild.github.io
[2]: https://github.com/electron/electron-rebuild
[3]: https://github.com/electron/electron-packager
57 changes: 42 additions & 15 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,56 +1,83 @@
#!/bin/sh
set -e

# Ensure final artifact directory exists:
mkdir -p ./packaged

# Pardon the comments, my shell is weak
# leaving myself lots of syntax reminders
# https://stackoverflow.com/questions/4341630/checking-for-the-correct-number-of-arguments

# Fail if incorrect number of arguments
if [ "$#" -ne 1 ]; then
echo "Usage: $0 BUILD NUMBER" >&2
exit 1
# if [ "$#" -ne 1 ]; then
# echo "Usage: $0 BUILD NUMBER" >&2
# exit 1
# fi

# Actually... conditionally accept a build number as an argument,
# Or just grab the highest number from the packaged directory as a kind of auto-increment

BUILD_NUM=$(ls packaged | sort -n | tail -1)
BUILD_NUM=$((BUILD_NUM+1))

if [ -n "$1" ]; then
BUILD_NUM=$1
fi

echo "Using build number $BUILD_NUM"

# Clear build directory:
rm -rf dist/

mkdir -p ./packaged
outdir="./packaged/${1}"
echo "Creating build directory at $outdir"
outdir="./packaged/${BUILD_NUM}"
echo "Building package in: $outdir"

# Build out directory already exists
if [ -e outdir ]; then
echo "$outdir already exists. Increment build number." >&2
exit 1
fi

# Found but not directory
# Found but not directory; seems unneccessary
# if ! [ -d "$1" ]; then
# echo "$1 not a directory" >&2
# exit 1
# fi

# copy contents of electron folder to dist/
# even though we compile it below... it requires the migrations folder....
# TODO: Can it be pulled in as a dependency with a loader!?
echo "Copying electron folder"
cp -r src/electron dist/
mkdir -p dist/electron
cp -r src/electron dist
cp src/index.html dist/index.html

# Delete any previously generated bundles
# In case we were changing names or something like that.
rm -rf src/*.bundle.*


# compile ui files to dist
npx webpack --config webpack.js
node ./scripts/production.js

# copy all bundled assets
cp -r src/*.bundle.* dist/

# copy package.json, required by electron to know how to start
cp package.json dist/
cp yarn.lock dist/

# see package.js
# Called from yarn postinstall; more context in the rebuild script
cp ./rebuild-better-sqlite3.sh dist/

# todo: This is installing dev dependencies which, because of webpack, should not be needed.
# When I use install --production, the final build complains it cannot find electron. Sigh.
# todo: This is installing dev dependencies which, because of esbuild, should not be needed.
# When I use install --production, the final build complains it cannot find electron
# Could probably just install --production then manually add electron? Or munge the copied
# package.json to remove all devDependencies except electron
# This will produce a smaller final install for end users, because the package step
# copies the node modules installed here into the final package
cd dist/
yarn
# todo: this only handles sqlite3... electron-rebuild (once fixed) would address all
# modules w/ native dependencies
yarn rebuild

cd ../

Expand Down
26 changes: 8 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
{
"name": "chronicles",
"version": "1.0.0",
"main": "index.js",
"main": "main.bundle.js",
"license": "UNLICENSED",
"scripts": {
"start": "yarn dev:webpack & yarn dev:electron",
"dev:webpack": "webpack-dev-server --config webpack.js --hot --inline --colors",
"dev:electron": "electron src/electron/index.js",
"start": "node ./scripts/dev.js",
"test": "mocha -r esm -r ts-node/register src/**/*.test.ts",
"test:one": "mocha -r ts-node/register -r esm",
"rebuild": "./rebuild-better-sqlite3.sh"
"postinstall": "./rebuild-better-sqlite3.sh"
},
"dependencies": {
"ajv": "^8.6.2",
"ajv-formats": "^2.1.0",
"better-sqlite3": "^7.4.3",
"cuid": "^2.1.8",
"electron-store": "^8.0.1",
"klaw": "^3.0.0",
"luxon": "^1.24.1",
"mime": "^2.5.2",
"mkdirp": "^1.0.4"
},
"devDependencies": {
Expand All @@ -32,19 +27,19 @@
"@types/react": "^17.0.27",
"@types/react-dom": "^17.0.9",
"@types/rehype-react": "^4.0.0",
"@udecode/plate": "^5.3.0",
"@udecode/plate": "^6.0.0",
"chai": "^4.2.0",
"chai-json-schema-ajv": "^5.2.4",
"css-loader": "^3.6.0",
"electron": "^15.1.1",
"electron-packager": "^15.0.0",
"electron-rebuild": "^3.2.3",
"emotion": "^10.0.27",
"error-overlay-webpack-plugin": "^0.4.1",
"esbuild": "^0.13.8",
"esm": "^3.2.25",
"evergreen-ui": "^6.4.0",
"html-webpack-plugin": "^4.3.0",
"klaw": "^3.0.0",
"lodash": "^4.17.21",
"luxon": "^1.24.1",
"mobx": "^5.15.4",
"mobx-react-lite": "^2.0.7",
"mocha": "^9.1.3",
Expand All @@ -64,15 +59,10 @@
"slate-hyperscript": "^0.66.0",
"slate-react": "^0.66.7",
"source-map-loader": "^1.0.0",
"style-loader": "^1.2.1",
"styled-components": "^5.3.1",
"ts-loader": "^7.0.5",
"ts-mdast": "^1.0.0",
"ts-node": "^10.3.0",
"typescript": "^4.3.5",
"unified": "^10.1.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
"unified": "^10.1.0"
}
}
103 changes: 103 additions & 0 deletions scripts/dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const esbuild = require('esbuild');
const cp = require('child_process');
const electron = require('electron')
const lodash = require('lodash')

// After successful rebuild, log results
function afterRebuild(name, cb) {
return (error, result) => {
if (error) {
console.error(`${name} bundle completed with error`, error);
} else {
console.log(`${name} bundle completed`);
restartElectron()
}
}
}

// After successful build, log results
function afterBuild(name) {
return ({errors, warnings, ...rest}) => {
if (errors.length) {
console.error(`${name} bundle completed with errors`, errors);
} else if (warnings.length) {
console.warn(`${name} bundle completed with warnings`, warnings);
} else {
console.log(`${name} bundle completed`);
}

// note: until I see errors or warnings, unsure if I should have any special behavior...
startElectron()
}
}

// build renderer bundle
esbuild.build({
entryPoints: ['src/index.tsx'],
outfile: 'src/renderer.bundle.js',
bundle: true,
platform: 'browser',
watch: {
onRebuild: afterRebuild('renderer')
},
}).then(afterBuild('renderer'), console.error);

// build preload bundle
esbuild.build({
entryPoints: ['src/preload/index.ts'],
outfile: 'src/preload.bundle.js',
bundle: true,
platform: 'node',
external: ['electron', 'electron-store', 'better-sqlite3'],
watch: {
onRebuild: afterRebuild('preload')
},
}).then(afterBuild('preload'), console.error);

// build electron main bundle
esbuild.build({
entryPoints: ['src/electron/index.js'],
outfile: 'src/main.bundle.js',
bundle: true,
platform: 'node',
external: ['electron', 'electron-store', 'better-sqlite3'],
watch: {
onRebuild: afterRebuild('main')
},
}).then(afterBuild('main'), console.error);

// For holding the spawned Electron main process, so it can
// be .kill()'ed on re-start
let eprocess;

// Track build completions, see below
let startCounter = 0;

// Start the electron main process
function startElectron() {
// Naive way to wait for all three bundles before starting the first time, since
// main bundle completes quickest
startCounter++
if (startCounter < 3) return;

console.log('starting electron')
eprocess = cp.spawn(`${electron}`, ['src/main.bundle.js'], { stdio: 'inherit'});

eprocess.on('error', (error) => {
console.error('electron error', error, error.message)
process.exit(1)
})
}

// Re-start the main process
const restartElectron = lodash.debounce(function startElectron() {
if (eprocess) eprocess.kill('SIGTERM');

console.log('starting electron')
eprocess = cp.spawn(`${electron}`, ['src/main.bundle.js'], { stdio: 'inherit'});

eprocess.on('error', (error) => {
console.error('electron error', error, error.message)
process.exit(1)
})
}, 200)
44 changes: 44 additions & 0 deletions scripts/production.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const esbuild = require('esbuild');
const cp = require('child_process');
const electron = require('electron');

// After successful build, log results
function afterBuild(name) {
return ({errors, warnings, ...rest}) => {
console.log(rest);
if (errors.length) {
console.error(`${name} bundle completed with errors`, errors);
process.exit(1)
} else if (warnings.length) {
console.warn(`${name} bundle completed with warnings`, warnings);
} else {
console.log(`${name} bundle completed`);
}
}
}

// build renderer bundle
esbuild.build({
entryPoints: ['src/index.tsx'],
outfile: 'src/renderer.bundle.js',
bundle: true,
platform: 'browser',
}).then(afterBuild('renderer'), console.error);

// build preload bundle
esbuild.build({
entryPoints: ['src/preload/index.ts'],
outfile: 'src/preload.bundle.js',
bundle: true,
platform: 'node',
external: ['electron', 'electron-store', 'better-sqlite3'],
}).then(afterBuild('preload'), console.error);

// build electron main bundle
esbuild.build({
entryPoints: ['src/electron/index.js'],
outfile: 'src/main.bundle.js',
bundle: true,
platform: 'node',
external: ['electron', 'electron-store', 'better-sqlite3'],
}).then(afterBuild('main'), console.error);
Loading

0 comments on commit acd5944

Please sign in to comment.