Appearance
支线篇-打包生成第一个桌面应用(骄傲自豪)
前言
许多小伙伴们通过阅读 环境篇-动手搭建我们的简历平台章节,将 Electron + React 搭建起来后,还没开始写几行代码,就迫不及待想打包成桌面应用,毕竟搞出自己第一个桌面应用,是一件很骄傲自豪的事情!!!为此,此章节将以 chapter-11分支为主,新增支线代码 chapter-14-app进行简易版的打包构建成桌面端应用。
⚠️ 请注意此章节为支线任务,非主线任务。只是为了快速圆大家一个桌面应用梦想。后续的章节,仍然以 chapter-11分支进行开发。
强烈在看章节内容的同时,结合仓库代码进行学习。👉 此项目代码在: chapter-14-app
electron 和 react 的关系
我们需要先搞清楚一下,electron 跟 react 的关系。
本地开发
当我们在本地开发时,运行的脚本命令是:
javascript
npm run start:main // 运行主进程
npm run start:render // 运行渲染进程
react 通过 webpack-dev-server
起了一个本地服务,我们通过 http://127.0.0.1:7001
就能访问我们的站点。Electron 是通过 BrowserWindow 创建了一个浏览器窗口,此窗口通过 loadURL 加载了我们的地址(你可以理解成 webview 形式),从而显示我们的网页。
javascript
// 创建浏览器窗口
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
devTools: true,
nodeIntegration: true,
},
});
if (isDev()) {
// 开发环境
mainWindow.loadURL(`http://127.0.0.1:7001`);
} else {
// 生产环境
mainWindow.loadURL(`file://${path.join(__dirname, '../dist/index.html')}`);
}
打包构建
当我们本地开发完成之后,需要打包上线,需要跑什么命令?不要想太复杂。它们也是分开打包的
javascript
npm run build:main // 打包主进程(对 app/main/electron.ts进行打包 )
npm run build:render // 打包渲染进程(也就是对 React 进行打包)
打包之后,我们 dist 目录就会存在相应的资源文件。如 index.html、electron.js 等。
关系
提问:抛开 Electron,我们常规的 React 项目,在打包之后,如何运行?
我们是通过点击 dist 下的 index.html 就能在浏览器页面中打开看效果(如果你发现没效果,请确保是以相对路径加载资源文件);但如果结合了 Electron,那么此时点击 dist 下的 index.html,在浏览器中打开,一般都会出错的。
如何理解?Electron 它内置了 Chromium 和 Node,试想一下,你能在 Chrome 浏览器的控制台中输出 process
吗?并不能,它会报错。
但如果你是通过 Electron 的 BrowserWindow 创建的浏览器窗口,去打印 process
,就能显示内容
这也是为什么 Electron 项目中 dist 下的 index.html 不能直接在 Chrome 浏览器运行,因为你的代码,可能用到了 Electron API、Node API,Chrome 浏览器无法识别这是什么东西。只有通过 Electron 生成的浏览器窗口,LoadURL 加载此页面,才能正常运行。
通过前面的代码可以看到,在生产环境下,我们 LoadURL 的是 dist 下的 index.html
javascript
if (isDev()) {
// 开发环境
mainWindow.loadURL(`http://127.0.0.1:7001`);
} else {
// 生产环境
mainWindow.loadURL(`file://${path.join(__dirname, '../dist/index.html')}`);
}
打包构建
上面了解了 Electron 和 React 的关系之后,接下来我们开始打包构建,此次项目中,将以 electron-builder 进行应用打包。请记住,我们以 chapter-11 分支代码进行打包,如果你是基于其他分支,请确保 webpack 的配置是正确的。
第一步:打包 Electron 和 React
在 webpack 文件夹下,新增 webpack.main.prod.js
,表示主进程在生产环境下的打包配置。
javascript
// webpack/webpack.main.prod.js
const path = require('path');
const baseConfig = require('./webpack.base.js');
const webpackMerge = require('webpack-merge');
const prodConfig = {
entry: path.resolve(__dirname, '../app/main/electron.ts'),
target: 'electron-main',
output: {
filename: 'electron.js',
path: path.resolve(__dirname, '../dist'),
},
devtool: 'inline-source-map',
// 👇 这里改成生产环境
mode: 'production',
};
module.exports = webpackMerge.merge(baseConfig, prodConfig);
新增 webpack.render.prod.js
,表示渲染进程在生产环境下的打包配置。
javascript
const path = require('path');
const webpackMerge = require('webpack-merge');
const baseConfig = require('./webpack.base.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const prodConfig = {
// 👇 这里改成生产环境
mode: 'production',
entry: {
index: path.resolve(__dirname, '../app/renderer/app.tsx'),
},
output: {
filename: '[name].[hash].js',
path: path.resolve(__dirname, '../dist'),
},
target: 'electron-renderer',
devtool: 'inline-source-map',
devServer: {
contentBase: path.join(__dirname, '../dist'),
compress: true,
host: '127.0.0.1', // webpack-dev-server启动时要指定ip,不能直接通过localhost启动,不指定会报错
port: 7001, // 启动端口为 7001 的服务
hot: true,
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader'],
},
{
test: /\.less$/,
exclude: /node_modules/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]__[hash:base64:5]',
},
},
},
'postcss-loader',
'less-loader',
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../app/renderer/index.html'),
filename: path.resolve(__dirname, '../dist/index.html'),
chunks: ['index'],
}),
],
};
module.exports = webpackMerge.merge(baseConfig, prodConfig);
紧接着,我们前往 package.json
中,添加打包命令
json
"scripts": {
// 本地开发
"start:main": "webpack --config ./webpack/webpack.main.dev.js && electron ./dist/electron.js",
"start:render": "webpack-dev-server --config ./webpack/webpack.render.dev.js",
// 👇 新增的生产打包命令
"build:main": "webpack --config ./webpack/webpack.main.prod.js",
"build:render": "webpack --config ./webpack/webpack.render.prod.js",
},
我们去执行一下 npm run build:main
和 npm run build:render
看看打包之后的 dist 文件,发现打包之后的 electron.js 不见了。
原因在于我们使用了 clean-webpack-plugin
插件,该插件在每次打包之前,都会将 dist 删除再新增。所以先打包的文件跟着被删掉了。我们来修改一下 webpack.base.js
javascript
// webpack/webpack.base.js
const path = require('path');
// const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
// 其他代码省略...
// 👇 注释此插件
// plugins: [new CleanWebpackPlugin()],
};
我们删除 dist 目录,再次打包试试
rm -rf dist
npm run build:main
npm run build:render
这次我们就发现,dist 目录下的内容都是正确的了。为此我们 Electron 和 React 均打包成功。
第二步:安装 electron-builder
根据官方文档,我们进行安装
npm install electron-builder --save-dev
第三步:添加打包命令进行打包
通过 electron-builder 官方文档的 快速上手,我们可以发现,只需要添加对应的一些配置即可实现打包。
配置属性很多,就不一一列举,小伙伴们们可以自行前往阅读一下~
根据教程,我们在 package.json 添加 build 属性
json
{
"build": {
"appId": "visResumeMook.ElectronReact", // 自定义 appId
"productName": "VisResumeMook", // 打包之后的程序名
"copyright": "Copyright © 2019 ${author}",
"files": ["dist/**/*", "package.json", "node_modules/"]
}
}
接着添加脚本命令
javascript
"scripts": {
"pack": "electron-builder --dir",
"dist": "electron-builder"
}
这时候我们就可以执行打包了。一个完整的打包构建命令为:
javascript
rm -rf dist
npm run build:main
npm run build:render
// 如果你的静态资源没发生改变,可以直接 pack,不用再重新打包主进程和渲染进程
npm run pack
运行一下,发现报错了
我们将 electron 放到 devDependencies 中,然后重新 install,再执行一下命令
npm run pack
执行结果还是出错,原因在于我们的应用入口文件写错了
我们检查一下 package.json 中的 main 属性,果然有问题,进行修改
json
{
// 👇 修改成打包后的入口文件
"main": "./dist/electron.js"
}
再执行一下命令,看看会不会打包成功
npm run pack
没问题,我们再去 dist 下面看看
我们双击打开,发现白屏,来看看报什么错
加载的 dist/index.html
有问题,通过控制台查看报错信息,我们发现这个 file://
链接有误
javascript
if (isDev()) {
// 开发环境
mainWindow.loadURL(`http://127.0.0.1:7001`);
} else {
// 生产环境
mainWindow.loadURL(`file://${path.join(__dirname, '../dist/index.html')}`);
}
很明显,这个 __dirname
好像不起作用。我们可以通过 webpack.definePlugin 添加此全局变量
修改 webpack.main.prod.js,我们为其添加此全局变量
javascript
// webpack/webpack.main.prod.js
plugins: [
// 👇 添加这个,用于打包后的主进程中正确获取__dirname
new webpack.DefinePlugin({
__dirname: '__dirname',
}),
];
然后重新打包一下
rm -rf dist
npm run build:main
npm run build:render
npm run pack
顺利打包之后,进入 dist ,双击点击我们的程序包,这时候就能正常运行了
第三步:生成多平台的安装包
当我们不配置任何平台相关信息时,将会根据你当前的设备,打包成对应的安装包。如果你想了解默认行为可点击这里,我们接下来进行 mac 和 win 的配置,配置参数太多,这里就直接上配置项了,大家可以去官网了解相关配置
json
"build": {
"appId": "visResumeMook.ElectronReact",
"productName": "VisResumeMook",
"copyright": "Copyright © 2019 ${author}",
"extends": null,
"files": [
"dist/**/*",
"package.json",
"node_modules/"
],
// 这是资源文件
"directories": {
"buildResources": "assets",
"output": "distAssets" // 这是打包之后安装包所在的文件夹
},
"mac": {
"target": [
"dmg",
"zip"
]
},
"dmg": {
"contents": [
{
"x": 130,
"y": 220
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
]
},
"win": {
"target": [
"nsis",
"msi"
]
}
},
之后我们再去执行一下打包。
npm run dist
我们进入到 distAssets 文件夹下,然后就会发现 dmg 安装包,进行安装即可
安装完后,我们就能愉快的使用此程序啦~
问题?
有小伙伴们可能会遇到一些问题,比如打包之后图片怎么没了,一些功能怎么消失了?不要慌,因为本章节知识支线任务,只是让小伙伴们体验一下构建成安装包的快乐,完整的构建打包请看此章节:打包篇-应用程序生产环境构建,看完之后,你遇到的打包问题都能迎刃而解~
最后
此章节以”粗暴“的方案实现,代码未经过优化和抽离,同时打包出来的程序体积很大,并未进行优化。这些操作都在主线任务的打包篇-Electron 打包体积优化去实现。