Webpack 是 Electron 项目中最常用的构建工具之一。虽然 Vite 等新工具逐渐流行,但 Webpack 凭借成熟的生态、丰富的插件和庞大的社区支持,依然是大量现有项目的基础设施。本节将提供一个可直接落地的 Webpack + Electron 配置方案,覆盖渲染进程与主进程的构建需求。
18.2.1 为什么还需要 Webpack
Electron 本身可以加载任意 HTML 文件,理论上你用原生的 <script> 标签也能写应用。但实际项目中,你需要:
- 使用 TypeScript、Sass、Less 等预编译语言
- 支持 JSX 或 Vue 单文件组件
- 按需加载、代码分割以优化启动速度
- 开发时热更新(HMR),提高迭代效率
- 生产环境压缩代码、分配缓存、减少体积
Webpack 恰好能在这些环节提供完整支持,而且它与 Electron 的进程模型可以很好地分工:渲染进程配置像普通前端项目,主进程配置则针对 Node.js 环境。
18.2.2 基础项目结构
假设我们的 Electron 项目结构如下:
my-app/
├── src/
│ ├── main/ # 主进程代码
│ │ └── index.ts
│ ├── preload/ # 预加载脚本
│ │ └── index.ts
│ └── renderer/ # 渲染进程(前端代码)
│ ├── index.html
│ ├── index.tsx # React 入口
│ └── App.tsx
├── package.json
├── webpack.main.config.js # 主进程 Webpack 配置
├── webpack.renderer.config.js # 渲染进程 Webpack 配置
└── tsconfig.json
我们将为主进程和渲染进程分别编写 Webpack 配置,因为这俩环境差异极大:主进程运行在 Node.js 中,目标为 CommonJS 模块;渲染进程运行在 Chromium 中,目标为浏览器环境。
18.2.3 渲染进程配置
渲染进程本质上就是一个现代前端应用,可以使用熟悉的 loader 和 plugin。
安装依赖:
npm install --save-dev webpack webpack-cli webpack-dev-server
npm install --save-dev html-webpack-plugin ts-loader css-loader style-loader
npm install --save-dev react react-dom @types/react @types/react-dom
webpack.renderer.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
entry: './src/renderer/index.tsx',
target: 'web', // 渲染进程运行在浏览器上下文
output: {
path: path.resolve(__dirname, 'dist/renderer'),
filename: '[name].[contenthash].js',
clean: true,
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|jpg|gif|svg|woff2?)$/,
type: 'asset/resource',
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/renderer/index.html',
}),
],
devServer: {
port: 3000,
hot: true,
static: {
directory: path.join(__dirname, 'dist/renderer'),
},
},
};
src/renderer/index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
在开发阶段,渲染进程可以独立启动 webpack-dev-server,方便前端工程师快速调试界面;而在 Electron 环境中,我们会让 Electron 窗口加载 http://localhost:3000。
18.2.4 主进程配置
主进程需要在 Node.js 环境中运行,因此 target 应该设为 'electron-main'(或 'node',但 'electron-main' 专为 Electron 做了优化)。同时,我们需要保留原生模块的 require 方式,不打包 Electron 内建模块和 node_modules。
webpack.main.config.js:
const path = require('path');
module.exports = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
entry: './src/main/index.ts',
target: 'electron-main',
output: {
path: path.resolve(__dirname, 'dist/main'),
filename: 'main.js',
clean: true,
},
resolve: {
extensions: ['.ts', '.js'],
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
externals: {
// 告诉 Webpack 不要打包 Electron 原生模块,保持外部引用
electron: 'commonjs2 electron',
},
node: {
__dirname: false, // 保证 __dirname 指向原始目录,而非 webpack 拼接的虚拟路径
__filename: false,
},
};
关键点说明:
externals将electron标记为外部依赖,这样 Webpack 不会把它打进 bundle,而是由 Electron 运行时提供。node.dirname: false避免 Webpack 对dirname的替换,保证文件路径的正确性(尤其在读取本地资源时很重要)。
18.2.5 预加载脚本配置
预加载脚本运行在一个介于 Node.js 和渲染进程之间的受限环境,通常使用 CommonJS 模块,且应该被单独编译。
可以新建一个配置 webpack.preload.config.js:
const path = require('path');
module.exports = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
entry: './src/preload/index.ts',
target: 'electron-preload',
output: {
path: path.resolve(__dirname, 'dist/preload'),
filename: 'preload.js',
},
resolve: {
extensions: ['.ts', '.js'],
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
};
然后在主进程中设置:
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, '../preload/preload.js'),
contextIsolation: true,
},
});
18.2.6 开发环境集成
理想状态下,开发者启动一个命令就能同时执行 Webpack 对主进程的编译、渲染进程的 dev server,并启动 Electron。
可以使用 concurrently 或 npm-run-all 来编排:
package.json 脚本:
"scripts": {
"start:renderer": "webpack serve --config webpack.renderer.config.js",
"build:main": "webpack --config webpack.main.config.js",
"build:preload": "webpack --config webpack.preload.config.js",
"dev:electron": "electron .", // 从 dist/main/main.js 启动
"dev": "concurrently \"npm run build:main\" \"npm run build:preload\" \"npm run start:renderer\" \"wait-on http://localhost:3000 && npm run dev:electron\""
}
这样,运行 npm run dev 会:
- 编译主进程和预加载脚本到
dist/ - 启动 dev server 在端口 3000
- 等待 dev server 可用后,启动 Electron 并加载
http://localhost:3000
为了让 Electron 加载 dev server,主进程代码中可以判断环境:
// src/main/index.ts
const isDev = process.env.NODE_ENV === 'development';
const win = new BrowserWindow({
// ...
});
if (isDev) {
win.loadURL('http://localhost:3000');
win.webContents.openDevTools();
} else {
win.loadFile(path.join(__dirname, '../renderer/index.html'));
}
18.2.7 生产构建
生产构建需要同时构建渲染进程、主进程和预加载脚本,并处理好资源路径。可以为 package.json 添加:
"scripts": {
"build:renderer": "webpack --mode production --config webpack.renderer.config.js",
"build:all": "npm run build:main && npm run build:preload && npm run build:renderer",
"package": "npm run build:all && electron-builder"
}
electron-builder 打包时通常会读取 dist/ 目录下的文件。注意将 electron-builder 的 files 配置指向正确的构建输出。
Electron Builder 配置片段:
"build": {
"files": [
"dist/**/*"
],
"directories": {
"output": "release"
}
}
18.2.8 实用建议
- 区分环境变量
使用 cross-env 设置 NODE_ENV,并在 Webpack 配置中通过 DefinePlugin 注入全局常量,方便在代码中判断开发/生产模式。
- 主进程代码分割
主进程通常不需要 Code Splitting,因为 Node.js 环境支持动态 require,但 Webpack 的 Tree Shaking 可以移除未使用的代码,保持主进程启动速度。
- 原生模块处理
如果你的依赖中包含 C++ 原生模块(如 sqlite3),不要在主进程 Webpack 中包含它,将其标记为 externals 或使用 node-loader 动态加载。打包时记住在 electron-builder 中重建原生模块。
- TypeScript 支持
主进程、预加载脚本、渲染进程分别使用独立的 tsconfig.json,确保每个环境的 target 和 lib 设置正确(例如渲染进程可以设为 ES2020,主进程设为 ES2019 并包含 Node 类型定义)。
- 使用
webpack-merge
如果配置中存在大量公共部分,可以用 webpack-merge 抽取共用的 rules、resolve,避免重复代码。
- 调试主进程
开发时可使用 VS Code 的 “Attach to Process” 功能,配合 --inspect 参数调试主进程 Webpack 编译后的代码。可以在启动脚本中加入:
electron --inspect=5858 .
通过这套 Webpack 配置方案,你可以用最熟悉的工具链来构建一个模块化、可维护的 Electron 应用。无论是主进程还是渲染进程,Webpack 都能提供一致的开发体验,并保证最终产物的优化质量。下一节我们会探讨 Vite 等更轻量的构建方式,看看它们如何进一步简化整个流程。