Skip to content

Commit

Permalink
koot-electron
Browse files Browse the repository at this point in the history
- make the `main` file can be hot reloaded
  • Loading branch information
Diablohu committed Dec 29, 2023
1 parent db78445 commit 72a4b71
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 129 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"editor.tabSize": 4,
"editor.insertSpaces": true,
"editor.codeActionsOnSave": {
"source.fixAll": true
"source.fixAll": "explicit"
},
"eslint.codeActionsOnSave.mode": "problems",
"eslint.validate": [
Expand Down
297 changes: 193 additions & 104 deletions packages/koot-electron/libs/modify-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
const fs = require('fs-extra');
const path = require('path');
const os = require('os');
const { exec } = require('child_process');
const webpack = require('webpack');
const sanitize = require('sanitize-filename');
const resolve = require('resolve');
const resolveFunc = require('resolve');
const inquirer = require('inquirer');
const merge = require('lodash/merge');
const sharp = require('sharp');
Expand All @@ -17,6 +18,7 @@ const {
keyConfigBuildDll,
keyConfigIcons,
CLIENT_ROOT_PATH,
buildManifestFilename,
} = require('koot/defaults/before-build');
const getDirDevTmp = require('koot/libs/get-dir-dev-tmp');
const getLogMsg = require('koot/libs/get-log-msg');
Expand All @@ -31,6 +33,9 @@ const defaultDistPackageJson = require('./defaults/dist-package-json');

// ============================================================================

/**
* 修改 appConfig
*/
const modifyConfig = async (appConfig) => {
if (appConfig[keyConfigBuildDll]) return;

Expand All @@ -54,10 +59,10 @@ const modifyConfig = async (appConfig) => {
if (typeof webpackBefore === 'function') await webpackBefore(...args);
};

// after - 如果是开发环境,自动启动;如果是生产环境,Pack Electron App
// after - 如果是生产环境,Pack Electron App
// 开发环境的操作整合在 webpack 输出文件后自动执行
appConfig.webpackAfter = async (...args) => {
if (process.env.WEBPACK_BUILD_ENV === 'dev') {
await afterBuildAutoOpen(...args);
} else if (!isFromStartCommand()) {
console.log(' ');
const { doPackaging } = await inquirer.prompt({
Expand All @@ -84,63 +89,147 @@ module.exports = modifyConfig;
// ============================================================================

const files = {};
let electronMainProcess;
let electronMainProcessActive = false;

const getElectronFilesFolder = (appConfig) =>
process.env.WEBPACK_BUILD_ENV === 'dev'
? getDirDevTmp('electron')
: path.resolve(appConfig[CLIENT_ROOT_PATH]);

const buildElectronMain = async (appConfig) => {
const dest = getElectronFilesFolder(appConfig);
const { electron: electronConfig = {}, dist } = appConfig;
const {
main,
mainOutput,
/** electron-builder 配置对象,会写入到打包结果的 `package.json` 中 */
build = {},
} = electronConfig;
const msg = getLogMsg(
false,
'electron',
__('building_main', { file: mainOutput })
);
const waiting = spinner(msg + '...');

files.main = path.resolve(dest, mainOutput);

const cwd = getCwd();
const projectPkg = require(path.resolve(cwd, 'package.json'));
const installedElectronDir = path.dirname(
resolve.sync('electron', {
basedir: cwd,
})
);
const installedElectronVersion = require(path.resolve(
installedElectronDir,
'package.json'
)).version;
const webpackConfig = await newWebpackConfig(
merge({}, defaultWebpackConfigMain, {
entry: {
main,
/**
* - 生产环境: 仅打包
* - 开发环境: 热更新打包,并自动打开主进程
*/
const buildElectronMain = async (appConfig) =>
new Promise(async (resolve, reject) => {
const dest = getElectronFilesFolder(appConfig);
const { electron: electronConfig = {}, dist } = appConfig;
const {
main,
mainOutput,
/** electron-builder 配置对象,会写入到打包结果的 `package.json` 中 */
build = {},
} = electronConfig;
const msg = getLogMsg(
false,
'electron',
__('building_main', { file: mainOutput })
);
const waiting = spinner(msg + '...');

files.main = path.resolve(dest, mainOutput);

const cwd = getCwd();
const projectPkg = require(path.resolve(cwd, 'package.json'));
const installedElectronDir = path.dirname(
resolveFunc.sync('electron', {
basedir: cwd,
})
);
const installedElectronVersion = require(path.resolve(
installedElectronDir,
'package.json'
)).version;

const thisConfig = await newWebpackConfig(
merge({}, defaultWebpackConfigMain, {
mode:
process.env.WEBPACK_BUILD_ENV === 'dev'
? 'development'
: 'production',
name: 'koot-electron-main',
entry: {
main,
},
output: {
path: dest,
},
node: {
global: true,
},
watch: process.env.WEBPACK_BUILD_ENV === 'dev' ? true : false,
optimization: {
splitChunks: false,
removeAvailableModules: false,
mergeDuplicateChunks: false,
concatenateModules: false,
},
performance: {
maxEntrypointSize: 100 * 1024 * 1024,
maxAssetSize: 100 * 1024 * 1024,
},
stats: 'summary',
}),
appConfig
);

thisConfig.plugins.push({
apply: (compiler) => {
// 输出文件后执行
compiler.hooks.afterEmit.tap(
'KootElectronBuildMainAfterEmitPlugin',
(compilation) => {
// 添加 package.json
fs.writeJsonSync(
path.resolve(dist, 'package.json'),
merge({}, defaultDistPackageJson, {
name: sanitize(appConfig.name || '')
.toLowerCase()
.replace(/ /g, '-'),
main: path.relative(dist, files.main),
description:
projectPkg.description ||
defaultDistPackageJson.description,
version:
projectPkg.version ||
defaultDistPackageJson.version,
author:
projectPkg.author ||
defaultDistPackageJson.author,
build,
devDependencies: {
electron: installedElectronVersion,
},
}),
{
spaces: 4,
}
);

// 如果是开发环境,自动杀死 Electron 主进程(如果有),并打开主进程
if (process.env.WEBPACK_BUILD_ENV === 'dev') {
if (electronMainProcessActive) {
killAll(electronMainProcess.pid).then(() => {
electronMainProcess = undefined;
openElectronMainProcess(appConfig);
});
} else {
// 等待 buildManifestFilename 文件出现
const fileFlagBuilding = path.resolve(
getDirDevTmp(),
buildManifestFilename
);
new Promise((resolve) => {
const wait = () =>
setTimeout(() => {
if (fs.existsSync(fileFlagBuilding))
return resolve();
wait();
}, 500);
wait();
}).then(() => {
openElectronMainProcess(appConfig);
});
}
}
}
);
},
output: {
path: dest,
},
}),
appConfig
);

// console.log('buildElectronMain', {
// dest,
// electronConfig,
// webpackConfig,
// appIcon,
// });
});

try {
await new Promise((resolve, reject) => {
webpack(webpackConfig, (err, stats) => {
try {
webpack(thisConfig, (err, stats) => {
if (err) return reject(err);

const info = stats.toJson();
Expand All @@ -160,73 +249,45 @@ const buildElectronMain = async (appConfig) => {
spinner(msg).succeed();
resolve(stats);
});
});
} catch (err) {
waiting.fail();
console.error(err);
throw err;
}

// 添加 package.json
await fs.writeJson(
path.resolve(dist, 'package.json'),
merge({}, defaultDistPackageJson, {
name: sanitize(appConfig.name || '')
.toLowerCase()
.replace(/ /g, '-'),
main: path.relative(dist, files.main),
description:
projectPkg.description || defaultDistPackageJson.description,
version: projectPkg.version || defaultDistPackageJson.version,
author: projectPkg.author || defaultDistPackageJson.author,
build,
devDependencies: {
electron: installedElectronVersion,
},
}),
{
spaces: 4,
} catch (err) {
waiting.fail();
console.error(err);
reject(err);
}
);
};
});

let opened = false;
// let doKill = false;
const afterBuildAutoOpen = async (appConfig) => {
if (opened) return;
/**
* 打开 Electron 主进程
*/
const openElectronMainProcess = async (appConfig) => {
if (electronMainProcessActive) return;

const { main } = files;
const cwd = getCwd();

const cmd = `electron ${main}`;
const chunks = cmd.split(' ');
const child = require('child_process').spawn(chunks.shift(), chunks, {
stdio: 'inherit',
shell: true,
cwd,
});
opened = true;
electronMainProcess = require('child_process').spawn(
chunks.shift(),
chunks,
{
stdio: 'inherit',
shell: true,
cwd,
detached: process.platform !== 'win32',
}
);
electronMainProcessActive = true;

child.on('close', () => {
// if (!doKill)
electronMainProcess.on('close', () => {
console.log(' ');
console.log(getLogMsg('warning', 'electron', __('dev_window_closed')));
console.log(' ');
opened = false;
// process.exit(1);
electronMainProcessActive = false;
});
child.on('error', (...args) => {
electronMainProcess.on('error', (...args) => {
console.error(...args);
});
// const exitHandler = async (...args) => {
// doKill = true;
// // child.kill('SIGINT');
// };
// process.on('exit', exitHandler);
// process.on('SIGINT', exitHandler);
// process.on('SIGUSR1', exitHandler);
// process.on('SIGUSR2', exitHandler);
// process.on('uncaughtException', exitHandler);
};

const packElectron = async (appConfig) => {
Expand Down Expand Up @@ -327,3 +388,31 @@ const modifyWebpackConfig = async (webpackConfig, appConfig) => {

return webpackConfig;
};

/**
* https://medium.com/@almenon214/killing-processes-with-node-772ffdd19aad
*
* kills the process and all its children
* If you are on linux process needs to be launched in detached state
* @param pid process identifier
* @param signal kill signal
*/
const killAll = (pid, signal = 'SIGTERM') =>
new Promise((resolve, reject) => {
if (process.platform === 'win32') {
exec(`taskkill /PID ${pid} /T /F`, (error, stdout, stderr) => {
// console.log("taskkill stdout: " + stdout)
// console.log("taskkill stderr: " + stderr)
if (error) {
// console.log("error: " + error.message)
return reject(error);
}
resolve(stdout);
});
} else {
// see https://nodejs.org/api/child_process.html#child_process_options_detached
// If pid is less than -1, then sig is sent to every process in the process group whose ID is -pid.
process.kill(-pid, signal);
resolve();
}
});
6 changes: 3 additions & 3 deletions packages/koot-electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
},
"homepage": "https://github.com/cmux/koot",
"engines": {
"node": ">= 8.9.0"
"node": ">= 18.0.0"
},
"dependencies": {
"electron": "^19.0.8",
"electron-builder": "^23.1.0"
"electron": "^28.1.0",
"electron-builder": "^24.9.1"
},
"devDependencies": {
"koot": "^0.15.12",
Expand Down
Loading

0 comments on commit 72a4b71

Please sign in to comment.