项目地址 希望收到一个小小的star
一、Electron版本实现
因为electron本身就有个chrome浏览器内核,从开发的角度上讲其实就是在开发浏览器的页面以及nodejs编写自己的逻辑和调用electron底层实现的api。如果只是单纯性想展示一个页面的话就直接把开发好的vue地址贴上去,创建一个窗口并且不需要任何除了窗口创建以外的node.js代码,但是有一些存在的缺陷
我的Electron开发方式:
- 前端与electron底层项目分离 开发环境下以localhost:port的形式渲染 打包后以域名形式或者打包的前端项目读取本地文件的形式 需要两个IDE 左边调完右边调(目前常用)
- electron与前端项目存在同一个文件夹下 通过webpack的配置让项目可以同时运行前端项目和electron项目 并且支持electron随代码改动进行重载 打包后以域名形式或者打包的前端项目读取本地文件的形式(昨天研究了一下 并且测试开发环境正常并且打包出来的exe是正常的)
确认开发方式
因为这个项目是非正式的项目 所以我们使用第二种方式 所以我们开始对文件目录结构进行调整
package.json调整
json
{
"name": "todo-list",
"displayName": "TodoList",
"version": "0.1.0",
"private": true,
"author": "yt",
"main": "main.js",
"scripts": {
"dev": "node ./runner/dev-runner.js",
"pack": "node ./runner/pack.js && node ./runner/build.js",
"pack-render": "node ./runner/pack-render.js",
"only-build": "node ./runner/build.js"
},
"dependencies": { // vue原来的依赖不变
"core-js": "^3.6.5",
"moment": "^2.30.1",
"vue": "^2.6.11",
"vue-contextmenujs": "^1.4.11",
"vue-uuid": "^3.0.0",
"vuetify": "^2.7.2",
"vuex": "^3.4.0"
},
"devDependencies": { // 新增webpack开发依赖 打包时不会把他们打包进去
"spinnies": "^0.5.1",
"chalk": "^2.4.1",
"del": "^6.1.1",
"@vue/cli-plugin-babel": "~4.5.4",
"@vue/cli-plugin-vuex": "~4.5.4",
"@vue/cli-service": "~4.5.4",
"deepmerge": "^4.3.1",
"sass": "^1.32.13",
"sass-loader": "^10.1.1",
"vue-template-compiler": "^2.6.11",
"babel-minify-webpack-plugin": "^0.3.1",
"electron": "27.3.6",
"vue-html-loader": "^1.2.4",
"vue-loader": "^15.2.4",
"vue-style-loader": "^4.1.0",
"webpack-cli": "^3.0.8",
"webpack": "^4.15.1",
"webpack-dev-server": "^3.1.4",
"webpack-hot-middleware": "^2.22.2",
"webpack-merge": "^4.1.3"
}
}
创建runner文件夹 以及开发环境与打包环境的webpack配置文件夹

开发环境热重载的功能编写(以下涉及到webpack的部分)
创建dev-runner.js 此部分是直接复用electron-vue的代码 为啥呢 虽然electron-vue这个项目废弃了 但是这个功能还是很好用的 并且集成了electron和vue在一个项目下 开启两个webpack 一个监听electron代码变动 去重载electron的应用 另一个去监听vue的代码变动
javascript
//runner/dev-runner.js
"use strict";
const chalk = require("chalk");
const electron = require("electron");
const path = require("path");
const { spawn } = require("child_process");
const webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server");
const webpackHotMiddleware = require("webpack-hot-middleware");
const mainConfig = require("./dev/webpack.main.config");
const rendererConfig = require("./dev/webpack.renderer.config");
let electronProcess = null;
let manualRestart = false;
let hotMiddleware;
function logStats(proc, data) {
let log = "";
log += chalk.yellow.bold(`┏ ${proc} Process ${new Array(19 - proc.length + 1).join("-")}`);
log += "\n\n";
if (typeof data === "object") {
data
.toString({
colors: true,
chunks: false,
})
.split(/\r?\n/)
.forEach((line) => {
log += " " + line + "\n";
});
} else {
log += ` ${data}\n`;
}
log += "\n" + chalk.yellow.bold(`┗ ${new Array(28 + 1).join("-")}`) + "\n";
console.log(log);
}
function startRenderer() {
return new Promise((resolve, reject) => {
rendererConfig.entry.renderer = rendererConfig.entry.renderer;
rendererConfig.mode = "development";
const compiler = webpack(rendererConfig);
hotMiddleware = webpackHotMiddleware(compiler, {
log: false,
heartbeat: 2500,
});
compiler.hooks.compilation.tap("compilation", (compilation) => {
compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync("html-webpack-plugin-after-emit", (data, cb) => {
hotMiddleware.publish({ action: "reload" });
cb();
});
});
compiler.hooks.done.tap("done", (stats) => {
logStats("Renderer", stats);
});
const server = new WebpackDevServer(compiler, {
contentBase: path.join(__dirname, "../"),
quiet: true,
hot: true,
before(app, ctx) {
// app.use(hotMiddleware)
ctx.middleware.waitUntilValid(() => {
resolve();
});
},
});
server.listen(9080);
});
}
function startMain() {
return new Promise((resolve, reject) => {
mainConfig.entry.main = [path.join(__dirname, "../src/main/index.js")];
mainConfig.mode = "development";
const compiler = webpack(mainConfig);
compiler.hooks.watchRun.tapAsync("watch-run", (compilation, done) => {
logStats("Main", chalk.white.bold("compiling..."));
hotMiddleware.publish({ action: "compiling" });
done();
});
compiler.watch({}, (err, stats) => {
if (err) {
console.log(err);
return;
}
logStats("Main", stats);
if (electronProcess && electronProcess.kill) {
manualRestart = true;
process.kill(electronProcess.pid);
electronProcess = null;
startElectron();
setTimeout(() => {
manualRestart = false;
}, 5000);
}
resolve();
});
});
}
function startElectron() {
var args = ["--inspect=5858", path.join(__dirname, "../dist/electron/main.js")];
// detect yarn or npm and process commandline args accordingly
if (process.env.npm_execpath.endsWith("yarn.js")) {
args = args.concat(process.argv.slice(3));
} else if (process.env.npm_execpath.endsWith("npm-cli.js")) {
args = args.concat(process.argv.slice(2));
}
electronProcess = spawn(electron, args);
electronProcess.stdout.on("data", (data) => {
electronLog(data, "blue");
});
electronProcess.stderr.on("data", (data) => {
electronLog(data, "red");
});
electronProcess.on("close", () => {
if (!manualRestart) process.exit();
});
}
function electronLog(data, color) {
let log = "";
data = data.toString().split(/\r?\n/);
data.forEach((line) => {
log += ` ${line}\n`;
});
if (/[0-9A-z]+/.test(log)) {
console.log(chalk[color].bold("┏ Electron -------------------") + "\n\n" + log + chalk[color].bold("┗ ----------------------------") + "\n");
}
}
function init() {
Promise.all([startRenderer(), startMain()])
.then(() => {
startElectron();
})
.catch((err) => {
console.error(err);
});
}
init();
开发环境所用的webpack配置
electron代码的webpack配置
javascript
//runner/dev/wepack.main.config.js
"use strict";
process.env.BABEL_ENV = "main";
const path = require("path");
const { dependencies } = require("../../package.json");
const webpack = require("webpack");
let mainConfig = {
entry: {
main: path.join(__dirname, "../../src/main/index.js"),
},
// 排除的打包依赖
externals: [...Object.keys(dependencies || {})],
module: {
rules: [
{
test: /.js$/,
use: "babel-loader",
exclude: /node_modules/,
},
{
test: /.node$/,
use: "node-loader",
},
],
},
node: {
__dirname: process.env.NODE_ENV !== "production",
__filename: process.env.NODE_ENV !== "production",
},
output: {
filename: "[name].js",
libraryTarget: "commonjs2",
path: path.join(__dirname, "../../dist/electron"),
},
plugins: [
new webpack.DefinePlugin({
__static: `"${path.join(__dirname, "../../static").replace(/\/g, "\\")}"`,
}),
],
resolve: {
extensions: [".js", ".json", ".node"],
},
target: "electron-main",
};
module.exports = mainConfig;
vue代码的webpack
javascript
//runner/dev/wepack.renderer.config.js
"use strict";
process.env.BABEL_ENV = "renderer";
const path = require("path");
const { dependencies } = require("../../package.json");
const webpack = require("webpack");
const MinifyPlugin = require("babel-minify-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader");
/**
* List of node_modules to include in webpack bundle
*
* Required for specific packages like Vue UI libraries
* that provide pure *.vue files that need compiling
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals
*/
let whiteListedModules = ["vue", "vuetify", "vue-contextmenujs"];
let rendererConfig = {
devtool: "#cheap-module-eval-source-map",
entry: {
renderer: path.join(__dirname, "../../src/renderer/main.js"),
},
externals: [...Object.keys(dependencies || {}).filter((d) => !whiteListedModules.includes(d))],
module: {
rules: [
{
test: /.s(c|a)ss$/,
use: ["vue-style-loader", "css-loader", "sass-loader"],
},
{
test: /.css$/,
use: ["vue-style-loader", "css-loader"],
},
{
test: /.html$/,
use: "vue-html-loader",
},
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@vue/cli-plugin-babel/preset"],
},
},
},
{
test: /.node$/,
use: "node-loader",
},
{
test: /.vue$/,
use: {
loader: "vue-loader",
},
},
{
test: /.(png|jpe?g|gif|svg)(?.*)?$/,
use: {
loader: "url-loader",
query: {
limit: 10000,
name: "imgs/[name]--[folder].[ext]",
},
},
},
],
},
node: {
__dirname: process.env.NODE_ENV !== "production",
__filename: process.env.NODE_ENV !== "production",
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({ filename: "styles.css" }),
new HtmlWebpackPlugin({
filename: "index.html",
template: path.resolve(__dirname, "../../src/index.ejs"),
title: require("../../package.json").name,
templateParameters(compilation, assets, options) {
return {
compilation: compilation,
webpack: compilation.getStats().toJson(),
webpackConfig: compilation.options,
htmlWebpackPlugin: {
files: assets,
options: options,
},
process,
};
},
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
},
nodeModules: process.env.NODE_ENV !== "production" ? path.resolve(__dirname, "../../node_modules") : false,
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
__static: `"${path.join(__dirname, "../../static").replace(/\/g, "\\")}"`,
}),
],
output: {
filename: "[name].js",
libraryTarget: "commonjs2",
path: path.join(__dirname, "../../dist/electron"),
},
resolve: {
alias: {
"@": path.join(__dirname, "../../src/renderer"),
vue$: "vue/dist/vue.esm.js",
},
extensions: [".js", ".vue", ".json", ".css", ".node"],
},
target: "electron-renderer",
};
module.exports = rendererConfig;
这样子我们就实现了基础的热重载功能 运行npn run dev即可
src文件夹目录结构

在根目录下创建一个src文件夹 并且创建两个子文件夹 main 与 renderer 一个是存放electron代码 一个是存放vue代码的 并且创建一个index.ejs 这个地方使用了webpack打包的HtmlWebpackPlugin 用这个index.ejs作为模板去生成最后的index.html 这个部分代替了vue下的public 所以vue的public可以删除了
html
// src/index.ejs
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= htmlWebpackPlugin.options.title %></title>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
<% if (htmlWebpackPlugin.options.nodeModules) { %>
<!-- Add `node_modules/` to global paths so `require` works properly in development -->
<script>
require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\/g, '\\') %>')
</script>
<% } %>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
</style>
</head>
<body>
<div id="app"></div>
<!-- Set `__static` path to static files in production -->
<% if (!process.browser) { %>
<script>
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\/g, '\\')
</script>
<% } %>
<!-- webpack builds are automatically injected -->
</body>
</html>
src/renderer的结构

将原来的整个vue移进来就好了 并且删除public文件夹
src/main的结构

一个index.js 作为主入口 后续的listener.js 和listeners下是我自己写的脚本
二、进行electrton的开发
src/main/index.js编写
javascript
const { app, BrowserWindow, Menu } = require("electron");
const { ListenerManager } = require("./listener");
const createWindow = () => {
Menu.setApplicationMenu(null); // null值取消顶部菜单栏
const win = new BrowserWindow({
transparent: false,
// resizable: false,
width: 400,
height: 700,
minHeight: 700,
minWidth: 400,
// resizable: false,
title: "Todo List",
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
frame: false, // 顶部操作栏
});
const winURL = process.env.NODE_ENV === "development" ? `http://localhost:9080` : `file://${__dirname}/index.html`;
// 后续可打包放在本地也可以 使用在线的地址
win.loadURL(winURL);
return win;
};
app.on("ready", () => {
const window = createWindow();
new ListenerManager(app, window);
if (!app.isPackaged) {
// 如果不打包,默认开启开发模式,开发者模式控制台
window.webContents.openDevTools({ mode: "detach" });
}
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});
src/main/listener.js
此脚本用于electron创建on与handle监听用
javascript
/*
* @Author: 羊驼
* @Date: 2022-01-25 11:30:52
* @LastEditors: 羊驼
* @LastEditTime: 2024-04-18 14:40:31
* @Description: electron 监听定义类
*/
const process = require("process");
const { ipcMain } = require("electron");
class BaseListener {
// 是否开启监听
enable = true;
// electron 存储的数据
window = null;
app = null;
mounted = false;
listener = {};
handler = {};
constructor(app, window, eventCollect, handlerCollect) {
this.window = window;
this.app = app;
setTimeout(() => {
try {
if (this.enable) {
let listener = this.listener;
let handler = this.handler;
for (let kv in listener) {
eventCollect.push(kv);
ipcMain.on(kv, listener[kv]);
}
for (let kv in handler) {
handlerCollect.push(kv);
ipcMain.handle(kv, handler[kv]);
}
this.mounted = true;
}
} catch (e) {
console.log(`脚本初始化出现问题 原因:${e}`);
// process.exit(1);
}
}, 100);
}
}
class ListenerManager {
listenerMap = new Map();
listenerCollect = [];
handlerCollect = [];
constructor(app, window) {
const fs = require("fs");
let scripts = fs.readdirSync(__dirname + "\listeners");
scripts.forEach((item) => {
item = item.replace(".js", "");
let listener = require(`./listeners/${item}`);
this.listenerMap.set(item, listener);
try {
let event = new listener(app, window, this.listenerCollect, this.handlerCollect);
this.listenerMap.set(item, event);
} catch (e) {
console.log(`该文件夹下不存在继承BaseListener的脚本 ${e}`);
process.exit(1);
}
});
this.checkRepeatListener();
}
checkRepeatListener() {
for (let value of this.listenerMap.values()) {
if (!value.mounted) {
setTimeout(() => {
this.checkRepeatListener();
}, 100);
return;
}
}
let set = new Set();
this.listenerCollect.forEach((item) => {
if (set.has(item)) {
console.log(`存在同名监听:${item}`);
// process.exit(1);
}
set.add(item);
});
set = new Set();
this.handlerCollect.forEach((item) => {
if (set.has(item)) {
console.log(`存在同名handle:${item}`);
// process.exit(1);
}
set.add(item);
});
}
}
module.exports = {
ListenerManager,
BaseListener,
};
src/main/listeners/common.js
javascript
const { BaseListener } = require("../listener");
class commonListener extends BaseListener {
listener = {};
handler = {
// 关闭
exit: () => {
this.app.quit();
},
// 最小化
minimize: () => {
this.window.minimize();
},
// 全屏调整
fullscreen: () => {
this.window.setFullScreen(!this.window.isFullScreen());
},
};
}
module.exports = commonListener;
electron的ipcMain.handle 与 vue中的ipcRenderer.invoke是一对 如果要获取handle的return数据 必须要await或者then
运行开发环境代码 npm run dev

编写App.vue工具栏的代码
html
<!--src/renderer/App.vue -->
<template>
<v-app :class="{dark}">
<v-system-bar
color="#EFEFEF"
height="36px"
window
app
>
<v-icon>mdi-infinity</v-icon>
<div class="app-name">Yangtuo To Do</div>
<v-spacer></v-spacer>
<v-btn
text
class="system-btn"
x-small
@click="invoke('minimize')"
><v-icon>mdi-window-minimize</v-icon></v-btn>
<v-btn
text
class="system-btn"
x-small
@click="invoke('fullscreen')"
> <v-icon>mdi-window-maximize</v-icon></v-btn>
<v-btn
text
class="system-btn"
x-small
@click="invoke('exit')"
> <v-icon>mdi-close</v-icon></v-btn>
</v-system-bar>
<!-- 这里使用我们自己封装的drawer 要根据页面宽度去解决侧边栏常驻 -->
<left-bar />
<!-- 编辑栏 -->
<edit-bar />
<!-- 建议栏 -->
<suggest-bar />
<v-main v-if="active_key">
<v-toolbar
:dark="dark"
color="transparent"
flat
:height="active_key.id==1?'120px':'84px'"
class="pr-2"
>
<v-toolbar-title>
<v-app-bar-nav-icon
@click="drawer=!drawer"
class="d-sm-flex d-md-none"
></v-app-bar-nav-icon>
<div class="pl-3 text-h5">{{active_key&&active_key.name}}</div>
<div
class="pl-3 mt-2 date"
v-if="active_key.id==1"
>{{today}}</div>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn
text
fab
small
v-if="active_key&&active_key.id==1"
@click="suggest_drawer=!suggest_drawer"
>
<v-icon>mdi-lightbulb-outline</v-icon>
</v-btn>
</v-toolbar>
<task-list />
</v-main>
</v-app>
</template>
<script>
import TaskList from "./components/TaskList.vue";
import mixin from "@/mixins/store";
import LeftBar from "./components/LeftBar.vue";
import SuggestBar from "./components/SuggestBar.vue";
import EditBar from "./components/EditBar.vue";
import moment from "moment";
moment.locale("zh-cn");
const { ipcRenderer } = require("electron");
export default {
mixins: [mixin],
name: "App",
components: {
TaskList,
LeftBar,
SuggestBar,
EditBar,
},
computed: {
today() {
return moment().format("M月D日 dddd");
},
},
methods: {
// 调用electron的代码
invoke(message) {
ipcRenderer.invoke(message);
},
},
};
</script>,
<style lang="scss">
html {
overflow-y: hidden !important;
}
.task-item {
background-color: #eee;
border-radius: 10px;
cursor: pointer;
&:hover {
background-color: #ccc;
}
}
.task-item.complete {
.v-list-item__title,
.v-list-item__subtitle {
text-decoration: line-through;
color: #ccc;
}
}
.menu_item_icon {
margin-right: 20px !important;
}
.edit-drawer {
z-index: 1001 !important;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
width: 100vw;
height: 100vh;
overflow-y: auto;
overflow-x: hidden;
background-color: #6478c7;
.v-system-bar {
z-index: 1002;
-webkit-app-region: drag;
.system-btn {
cursor: pointer;
-webkit-app-region: no-drag !important;
}
.v-icon {
font-size: 1rem !important;
}
}
.menu_item_label {
margin-left: 10px !important;
font-size: 16px;
}
.app-name {
font-size: 14px;
user-select: none;
}
.date {
font-size: 14px;
}
&.dark {
.todo .v-input__slot {
background-color: #fff !important;
input {
color: #000 !important;
&::placeholder {
color: #ccc !important;
}
}
.v-icon,
.white-text {
color: #616161 !important;
}
}
.title {
color: #fff;
}
$subtitle: #616161;
.task-item {
background-color: #f6f6f6;
color: #717171;
.v-list-item__subtitle {
color: $subtitle;
}
.v-input--selection-controls__input .v-icon {
color: $subtitle !important;
}
.v-icon {
color: $subtitle !important;
}
}
}
}
</style>
三、electron打包的实现
编写prod/webpack.main.config.js的配置
javascript
"use strict";
process.env.BABEL_ENV = "main";
const CopyWebpackPlugin = require("copy-webpack-plugin");
const path = require("path");
const webpack = require("webpack");
const { dependencies } = require("../../package.json");
let mainConfig = {
target: "electron-main",
entry: {
main: path.join(__dirname, "../../src/main/index.js"),
},
// 没有用到打包依赖 所以全部排除
externals: [...Object.keys(dependencies || {})],
module: {
rules: [
{
test: /.js$/,
use: "babel-loader",
exclude: /node_modules/,
},
{
test: /.node$/,
use: "node-loader",
},
],
},
node: {
__dirname: false,
__filename: false,
},
output: {
filename: "[name].js",
libraryTarget: "commonjs2",
path: path.join(__dirname, "../../dist/electron"),
},
plugins: [
new webpack.DefinePlugin({
__static: `"${path.join(__dirname, "../../static").replace(/\/g, "\\")}"`,
}),
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: '"production"',
},
}),
new CopyWebpackPlugin([
{
from: path.join(__dirname, "../../src/main"),
to: path.join(__dirname, "../../dist/electron"),
ignore: ["index.js"],
},
]),
],
resolve: {
extensions: [".js", ".json", ".node"],
},
performance: {
maxEntrypointSize: 1024 * 1024 * 10,
maxAssetSize: 1024 * 1024 * 20,
},
optimization: {
minimize: true,
},
};
module.exports = mainConfig;
编写prod/webpack.renderer.config.js的配置
javascript
"use strict";
process.env.BABEL_ENV = "renderer";
const path = require("path");
const { dependencies } = require("../../package.json");
const webpack = require("webpack");
const MinifyPlugin = require("babel-minify-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader");
/**
* List of node_modules to include in webpack bundle
*
* Required for specific packages like Vue UI libraries
* that provide pure *.vue files that need compiling
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals
*/
let whiteListedModules = ["vue", "vuetify", "vue-contextmenujs"];
let rendererConfig = {
mode: "production",
devtool: false,
entry: {
renderer: path.join(__dirname, "../../src/renderer/main.js"),
},
externals: [],
module: {
rules: [
{
test: /.s(c|a)ss$/,
use: ["vue-style-loader", "css-loader", "sass-loader"],
},
{
test: /.css$/,
use: ["vue-style-loader", "css-loader"],
},
{
test: /.html$/,
use: "vue-html-loader",
},
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@vue/cli-plugin-babel/preset"],
},
},
},
{
test: /.node$/,
use: "node-loader",
},
{
test: /.vue$/,
use: {
loader: "vue-loader",
},
},
{
test: /.(png|jpe?g|gif|svg)(?.*)?$/,
use: {
loader: "url-loader",
query: {
limit: 10000,
name: "imgs/[name]--[folder].[ext]",
},
},
},
],
},
node: {
__dirname: false,
__filename: false,
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({ filename: "styles.css" }),
new HtmlWebpackPlugin({
filename: "index.html",
template: path.resolve(__dirname, "../../src/index.ejs"),
title: require("../../package.json").name,
templateParameters(compilation, assets, options) {
return {
compilation: compilation,
webpack: compilation.getStats().toJson(),
webpackConfig: compilation.options,
htmlWebpackPlugin: {
files: assets,
options: options,
},
process,
};
},
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
},
nodeModules: false,
}),
// new MinifyPlugin(),
new CopyWebpackPlugin([
{
from: path.join(__dirname, "../../static"),
to: path.join(__dirname, "../../dist/electron/static"),
ignore: [".*"],
},
{
from: path.join(__dirname, "../../package.json"),
to: path.join(__dirname, "../../dist/electron/package.json"),
},
]),
new webpack.DefinePlugin({
"process.env.NODE_ENV": '"production"',
}),
new webpack.LoaderOptionsPlugin({
minimize: true,
}),
],
optimization: {
minimize: true,
splitChunks: {
chunks: "initial",
minChunks: 2,
},
},
performance: {
maxEntrypointSize: 1024 * 1024 * 10,
maxAssetSize: 1024 * 1024 * 20,
hints: "warning",
},
output: {
filename: "[name].js",
libraryTarget: "commonjs2",
path: path.join(__dirname, "../../dist/electron"),
},
resolve: {
alias: {
"@": path.join(__dirname, "../../src/renderer"),
vue$: "vue/dist/vue.esm.js",
},
extensions: [".js", ".vue", ".json", ".css", ".node"],
},
target: "electron-renderer",
};
module.exports = rendererConfig;
编写webpack打包electron与vue用的代码 runner/pack.js
javascript
process.env.NODE_ENV = "production";
const chalk = require("chalk");
const del = require("del");
const webpack = require("webpack");
const Spinnies = require("spinnies");
const mainConfig = "./prod/webpack.main.config";
const rendererConfig = "./prod/webpack.renderer.config";
const errorLog = chalk.bgRed.white(" ERROR ") + " ";
const okayLog = chalk.bgGreen.white(" OKAY ") + " ";
const { Worker, isMainThread, parentPort } = require("worker_threads");
function build() {
console.time("build");
del.sync(["dist/**", "build/**"]);
const spinners = new Spinnies({ color: "blue" });
spinners.add("main", { text: "main building" });
spinners.add("renderer", { text: "renderer building" });
let results = "";
// m.on('success', () => {
// process.stdout.write('\x1B[2J\x1B[0f')
// console.log(`\n\n${results}`)
// console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
// process.exit()
// })
function handleSuccess() {
process.stdout.write("\x1B[2J\x1B[0f");
console.log(`\n\n${results}`);
console.timeEnd("build");
process.exit();
}
Promise.all([
pack(mainConfig)
.then((result) => {
results += result + "\n\n";
spinners.succeed("main", { text: "main build success!" });
})
.catch((err) => {
spinners.fail("main", { text: "main build fail :(" });
console.log(`\n ${errorLog}failed to build main process`);
console.error(`\n${err}\n`);
process.exit(1);
}),
pack(rendererConfig)
.then((result) => {
results += result + "\n\n";
spinners.succeed("renderer", { text: "renderer build success!" });
})
.catch((err) => {
spinners.fail("renderer", { text: "renderer build fail :(" });
console.log(`\n ${errorLog}failed to build renderer process`);
console.error(`\n${err}\n`);
process.exit(1);
}),
]).then(handleSuccess);
}
function pack(config) {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename);
const subChannel = new MessageChannel();
worker.postMessage({ port: subChannel.port1, config }, [subChannel.port1]);
subChannel.port2.on("message", ({ status, message }) => {
switch (status) {
case "success":
return resolve(message);
case "error":
return reject(message);
}
});
});
}
function runPack(config) {
return new Promise((resolve, reject) => {
config = require(config);
config.mode = "production";
webpack(config, (err, stats) => {
if (err) reject(err.stack || err);
else if (stats.hasErrors()) {
let err = "";
stats
.toString({
chunks: false,
modules: false,
colors: true,
})
.split(/\r?\n/)
.forEach((line) => {
err += ` ${line}\n`;
});
reject(err);
} else {
resolve(
stats.toString({
chunks: false,
colors: true,
})
);
}
});
});
}
if (isMainThread) build();
else {
parentPort.once("message", ({ port, config }) => {
// assert(port instanceof MessagePort)
runPack(config)
.then((result) => {
port.postMessage({
status: "success",
message: result,
});
})
.catch((err) => {
port.postMessage({
status: "error",
message: err,
});
})
.finally(() => {
port.close();
});
});
}
编写生成exe的脚本
我这边使用的是electron-packager 没有使用electron-builder
javascript
const config = require("../package.json");
let name = `./${config.displayName}-win32-x64`;
const del = require("del");
del.sync([`${name}/**`]);
const spawnObj = require("child_process").exec(`electron-packager ./dist/electron ${config.displayName} --overwrite --asar `, {
encoding: "utf-8",
stdio: "pipe",
});
spawnObj.stdout.on("data", function (data) {
console.log(data);
});
spawnObj.stderr.on("data", (data) => {
console.log(data);
});
spawnObj.on("exit", (code) => {
if (code === 0) {
console.log("打包完成");
} else {
console.log("打包失败");
}
});
为package.json添加命令
bash
"pack": "node ./runner/pack.js && node ./runner/build.js"
运行脚本



