electron

electron

Posted by nomadli on June 27, 2023

NodeJS安装升级

brew install node
brew update # cd $(brew --repo) #HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles'
brew upgrade
sudo npm cache clean -f         #清除缓存
npm view npm versions           #所有版本
npm view npm version            #最新版本
npm view node versions
sudo npm install npm@latest -g  #升级到最新版本的npm
sudo npm install npm@xx -g      #升级到指定版本npm
npm list 清单
npm config set registry=https://registry.npmmirror.com
npm config set disturl=https://registry.npmmirror.com/-/binary/node

项目初始化

    mkdir project_name
    cd project_name
    npm init    #entry point main.js
    #npm install --arch=ia32 --platform=win32
    npm config set ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/
    npm install --save-dev electron
    //vi package.json "scripts": {"start": "electron ."},
    //touch main.js
    //touch index.html or use nomadli.com/index.html
    //touch preload.js
//vi main.js
const { app, BrowserWindow, Menu, ipcMain } = require('electron')
const path = require('path')

//let win: BrowserWindow | null = null;
const createWindow = () => {
    //if (win !== null) {
    //    return;
    //}
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            //__dirname 当前js的目录, preload在启动进程前运行,因此preload.js还在主进程
            preload: path.join(__dirname, 'preload.js'),
            // 打开这选项,可以在渲染进程使用node
            // 否则就只能在主进程使用node,所有消息靠进程间通信实现
            // 但是electron官方非常不推荐,这样不太安全
            nodeIntegration: true,
            contextIsolation: false,
            webSecurity: false
        },
        icon: '/path/to/icon.png',
        frame: false // 设置无边框窗体
    })

    const menu = Menu.buildFromTemplate([
    {
        label: app.name,
        submenu: [
            {
                //win.webContents == ipcRenderer.send
                click: () => win.webContents.send('update-counter', 1),
                label: 'Increment'
            },
            {
                click: () => win.webContents.send('update-counter', -1),
                label: 'Decrement'
            }
        ]
    }])
    Menu.setApplicationMenu(menu)

    win.loadFile('index.html')
    //win.on('close', () => win = null);
    
    win.webContents.openDevTools() // Open the DevTools.
}

app.whenReady().then(() => {
    console.log("platform = " +process.platform)
    console.log("electron version = " + process.versions['electron'])
    console.log("node version = " + process.versions['node'])
    console.log("chrome version = " + process.versions['chrome'])

    ipcMain.on('set-title', (event, title) => {
        const webContents = event.sender
        const win = BrowserWindow.fromWebContents(webContents)
        win.setTitle(title)
    })
    ipcMain.handle('dialog:openFile', async () => {
        const { canceled, filePaths } = await dialog.showOpenDialog()
        if (!canceled) {
            return filePaths[0]
        }
    })
    ipcMain.on('async-req', (event, arg) => {
        console.log(arg)
        event.reply('async-rps', 'pong')
    })
    ipcMain.on('sync-req', (event, arg) => {
        console.log(arg)
        event.returnValue = 'pong'
    })

    createWindow()
    if (process.platform === 'darwin') {
        app.on('activate', () => {
            if (BrowserWindow.getAllWindows().length === 0) {
                createWindow()
            }
        })
    }
})

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

//app.on('activate', () => {
//    createWindow();
//})
//vi preload.js 这里是render进程
const { contextBridge, ipcRenderer } = require('electron')

//mainword是NodeJS进程 render的js调用${nodejs.chrome()} window.nodejs.ping() window.nodejs.setTitle(title)
contextBridge.exposeInMainWorld('nodejs', {
    node: () => process.versions.node,
    chrome: () => process.versions.chrome,
    electron: () => process.versions.electron,
    send: ipcRenderer.send, //直接运行render发送进程通信
    aping: () => ipcRenderer.send('async-req', 'ping'),
    ping: () => {const r = ipcRenderer.sendSync('sync-req', 'ping') console.log(r)},
    setTitle: (title) => ipcRenderer.send('set-title', title),
    openFile: () => ipcRenderer.invoke('dialog:openFile'),
    //window.nodejs.menuCounter((event, value) => {event.sender.send('counter-value', newValue)})  其中event.sender.send 相当于ipcRenderer.send
    menuCounter: (callback) => ipcRenderer.on('update-counter', callback)
})

ipcRenderer.on('async-rps', (_event, arg) => {
    console.log(arg)
})

window.addEventListener('DOMContentLoaded', () => {
    const item = document.getElementById(item_id)
    if (item) {
        item.innerText = "hello change world"
    }
})
  • 创建NodeJS工程、引入Electron模块、通过Electron的app声明周期调用chrome的显示.整体c/s模型
  • app 模块生命周期
  • BrowserWindow 窗口管理
  • path NodeJS path 模块
  • reload.js 用来在两个进程通信只能使用 Electron的染进程 Node.js的events、timers、url 和Polyfilled全局模块Buffer、process、clearImmediate、setImmediate

项目打包

npm install --save-dev @electron-forge/cli
npx electron-forge import
npm run make
//forge.config.js 中macos 签名
module.exports = {
    packagerConfig: {
        osxSign: {},
        osxNotarize: {
            tool: 'notarytool',
            appleId: process.env.APPLE_ID,
            appleIdPassword: process.env.APPLE_PASSWORD,
            teamId: process.env.APPLE_TEAM_ID
        }
    }
}

//window 签名
module.exports = {
    makers: [
        {
            name: '@electron-forge/maker-squirrel',
            config: {
                certificateFile: './cert.pfx',
                certificatePassword: process.env.CERTIFICATE_PASSWORD,
                iconUrl: 'https://url/to/icon.ico',
                setupIcon: '/path/to/icon.ico',
                //其它 都设置icon: '/path/to/icon.png'
            }
        },
        //多平台
        {
          "name": "@electron-forge/maker-zip",
          "platforms": ["darwin", "linux", "windows"],
          "config": {}
        }
    ]
}
// package.json 中添加多平台
  "config": {
    "forge": {
      "makers": [
        {
          "name": "@electron-forge/maker-zip",
          "platforms": ["darwin", "linux", "windows"],
          "config": {}
        }
      ]
    }
  },
macos .icns 1024x1024  Windows .ico 256x256 linux png 512x512
//window macos
module.exports = {
    packagerConfig: {
        icon: '/path/to/icon' // no file extension required
    }
}
//linux
{
    name: '@electron-forge/maker-deb',
    config: {
        options: {
            icon: '/path/to/icon.png'
        }
    }
}
images/
├── icon.png
├── icon@2x.png
└── icon@3x.png
  • forge 组合了electron-packager、 @electron/osx-sign、electron-winstaller 等

vscode 调试

{
    "version": "0.2.0",
    "compounds": [
        {
            "name": "Electron",
            "configurations": ["NodeJS", "Renderer"],
            "stopAll": true
        }
    ],
    "configurations": [
        {
            "name": "Renderer",
            "port": 9222,
            "request": "attach",
            "type": "chrome",
            "webRoot": "${workspaceFolder}"
        },
        {
            "name": "NodeJS",
            "type": "node",
            "request": "launch",
            "cwd": "${workspaceFolder}",
            "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
            "windows": {
                "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
            },
            "args": [".", "--remote-debugging-port=9222"],
            "outputCapture": "std",
            "console": "integratedTerminal"
        }
    ]
}

问题

  • 主进程是NodeJS 无法直接操作dom即无法访问渲染器上下文
  • 使用预加载完成多进程通信preload.js

非前端 Electron+TypeScript+Webpack不带vue

npm init
#安装electron依赖库
npm install --save-dev electron
npm install --save-dev webpack webpack-cli
#electron需要单独打包主进程和渲染进程,共用配置需要抽取需要merge合并共用和私有
npm install --save-dev webpack-merge
npm install --save-dev webpack-node-externals 打包过程中排除node_modules
#webpack的loader
#解决css打包
npm install --save-dev css-loader style-loader
#解决native的node文件打包
npm install --save-dev node-loader
#解决typescript文件打包
npm install --save-dev ts-loader
#解决其它文件打包
npm install --save-dev file-loader
#wepack 打包html的plugin
npm install --save-dev html-webpack-plugin
npm install --save-dev typescript
#typescript类型库,node库和webpack库 @types/electron不需要, electron模块已经自带了
npm install --save-dev @types/node @types/webpack
#打包工具
npm install --save-dev @electron-forge/cli
#写代码
#创建tsconfig.json 或执行下面命令
tsc init
#编辑配置并执行调试
npm run webpack-main #执行主进程打包
npm run webpack-render #执行渲染进程打包
npm start
#打包
npm exec --package=@electron-forge/cli -c "electron-forge import"
npm run package
npm run make
//tsconfig.json
{
    "compilerOptions": {
        "target": "ES2018",
        "module": "CommonJS",
        "lib": [ "dom", "esnext" ],
        "strict": true,
        "noImplicitReturns": true,
        "moduleResolution": "Node",
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "allowJs": true,
        "resolveJsonModule": true
    },
    "exclude": [ "node_modules" ],
    "include": [ "src/main", "src/renderer" ]
}

//package.json scripts节添加:
"webpack-main": "webpack --config webpack.main.config.js",
"webpack-render": "webpack --config webpack.render.config.js"
//webpack.base.config.js
module.exports = {  
    module: {  
        rules: [  
            // typescript  
            {  
                test: /\.ts$/,  
                use: 'ts-loader',  
                exclude: /node_modules/  
            },  
            // css (style loader在前 css loader在后)  
            {  
                test:/\.css$/,  
                use: [ 'style-loader', 'css-loader'],  
                exclude: /node_modules/  
            },  
            // .node native (这里例子中用不到)
            {  
                test: /\.node$/,  
                exclude: /node_modules/,  
                use: 'node-loader'   
			}  
        ]  
    }  
};

//webpack.main.config.js
const path = require('path');  
const webpack = require('webpack');  
const { merge } = require('webpack-merge');  
const nodeExternals = require('webpack-node-externals');  
const webpackBaseConfig = require('./webpack.base.config');  
  
module.exports = merge(webpackBaseConfig, {  
    mode: 'development',  
    target: 'node',  
    entry: path.join(__dirname, 'src/main/main.ts'),  
    output: {  
	    // 注意输出目录
        path: path.join(__dirname, 'dist/main'),  
        filename: 'main.js'  
    },  
    externals: [nodeExternals()],  
    plugins: [  
        new webpack.EnvironmentPlugin({  
            NODE_ENV: 'development'  
        })  
    ],  
    node: {  
        __dirname: false,  
        __filename: false  
    }  
});

//webpack.render.config.js
const path = require('path');
const webpack = require('webpack');
const { merge } = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpackBaseConfig = require('./webpack.base.config');

module.exports = merge(webpackBaseConfig, {
    mode: 'development',
    target: 'electron-renderer',
    entry: path.join(__dirname, 'src/render/render.ts'),
    output: {
        path: path.join(__dirname, 'dist/render'),
        filename: 'render.js'
    },
    plugins: [
        new webpack.EnvironmentPlugin({
            NODE_ENV: 'development'
        }),
        new HtmlWebpackPlugin({
            inject: 'body',
            scriptLoading: 'defer',
            minify: false,
            filename: 'index.html',
            template: path.join(__dirname, 'src/render/index.html')
        }),
    ],
});