Webpack 插件

使用 webpack 转换和打包 Electron Forge 应用的代码。

此插件简化了设置标准 webpack 工具以编译主进程代码和渲染进程代码的过程,并内置支持渲染进程中的 热模块替换 (HMR) 以及对多个渲染器的支持。

安装

npm install --save-dev @electron-forge/plugin-webpack

用法

插件配置

您必须提供两个 webpack 配置文件:一个用于主进程的 mainConfig,另一个用于渲染进程的 renderer.config。完整的配置选项可在 API 文档中找到,位于 WebpackPluginConfig

例如,这是 Forge 的 配置 中取自 webpack 模板 的配置。

module.exports = {
  // ...
  plugins: [
    {
      name: '@electron-forge/plugin-webpack',
      config: {
        mainConfig: './webpack.main.config.js',
        renderer: {
          config: './webpack.renderer.config.js',
          entryPoints: [{
            name: 'main_window',
            html: './src/renderer/index.html',
            js: './src/renderer/index.js',
            preload: {
              js: './src/preload.js'
            }
          }]
        }
      }
    }
  ]
  // ...
};

项目文件

此插件为主进程以及每个渲染进程和预加载脚本生成单独的入口。

您需要在项目文件中执行两件事才能使此插件正常工作。

package.json

首先,您需要将 package.json 文件中的 main 入口指向 "./.webpack/main",如下所示

package.json
{
  "name": "my-app",
  "main": "./.webpack/main",
  // ...
}

主进程代码

其次,所有 loadURLpreload 路径都需要引用此插件将为您定义的魔法全局变量。

每个入口点根据分配给您的入口点的名称定义了两个全局变量

  • 渲染器的入口点将以 _WEBPACK_ENTRY 为后缀

  • 渲染器的预加载脚本将以 _PRELOAD_WEBPACK_ENTRY 为后缀

在前面示例中的 main_window 入口点的情况下,全局变量将分别命名为 MAIN_WINDOW_WEBPACK_ENTRYMAIN_WINDOW_PRELOAD_WEBPACK_ENTRY。下面给出了如何使用它们的示例

main.js
const mainWindow = new BrowserWindow({
  webPreferences: {
    preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY
  }
});

mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);

这些变量仅在主进程中定义。如果您需要在渲染器中使用其中一个路径(例如,将预加载脚本传递给 <webview> 标签),您可以使用同步 IPC 往返传递魔法变量值。

main.js
// make sure this listener is set before your renderer.js code is called
ipcMain.on('get-preload-path', (e) => {
  e.returnValue = WINDOW_PRELOAD_WEBPACK_ENTRY;
});

与 TypeScript 一起使用

如果您将 webpack 插件与 TypeScript 一起使用,则需要手动声明这些魔法变量以避免编译器错误。

main.js(主进程)
declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;

高级配置

webpack-dev-server

Forge 的 webpack 插件使用 webpack-dev-server 来帮助您在开发模式下快速迭代渲染进程代码。在启用 webpack 插件的情况下运行 electron-forge start 将启动一个开发服务器,该服务器可以通过插件配置进行配置。

devServer

在开发模式下,您可以通过在 Forge Webpack 插件配置中设置 devServer 来更改大多数 webpack-dev-server 选项。

插件配置
{
  name: '@electron-forge/plugin-webpack',
  config: {
    // other Webpack plugin config...
    devServer: {
      stats: 'verbose'
    }
    // ...
  }
}

devContentSecurityPolicy

在开发模式下,您可以通过在 Forge Webpack 插件配置中设置 devContentSecurityPolicy 来设置 内容安全策略 (CSP)

{
  name: '@electron-forge/plugin-webpack',
  config: {
    // other Webpack plugin config...
    devContentSecurityPolicy: 'default-src \'self\' \'unsafe-inline\' data:; script-src \'self\' \'unsafe-eval\' \'unsafe-inline\' data:',
    // other Webpack plugin config...
    mainConfig: './webpack.main.config.js',
    renderer: {
      /* renderer config here, see above section */
    }
  }
}

如果您希望在开发中使用 源代码映射,则需要为 script-src 指令设置 'unsafe-eval'。使用 'unsafe-eval' 将导致 Electron 本身在 DevTools 控制台中触发有关启用该值的警告,只要您 不要在生产环境中设置该值,这通常是可以的。

原生 Node 模块

如果您使用 WebpackTypeScript + Webpack 模板创建应用程序,则原生模块大多可以开箱即用。

如果您手动设置插件,可以通过在 Webpack 配置文件中的 module.rules 配置中添加以下两个加载器来使原生模块工作。请确保您安装了 node-loader@vercel/webpack-asset-relocator-loader 作为开发依赖项。

npm install --save-dev node-loader @vercel/[email protected]

Electron Forge 对资产重定位加载器进行了猴子补丁,以便使其能够与 Electron 正确配合使用,因此版本已被固定以确保兼容性。如果您升级该版本,则需自行承担风险。

webpack.main.config.js
module.exports = {
  module: {
    rules: [
      {
        // We're specifying native_modules in the test because the asset
        // relocator loader generates a "fake" .node file which is really
        // a cjs file.
        test: /native_modules\/.+\.node$/,
        use: 'node-loader'
      },
      {
        test: /\.(m?js|node)$/,
        parser: { amd: false },
        use: {
          loader: '@vercel/webpack-asset-relocator-loader',
          options: {
            outputAssetBase: 'native_modules'
          }
        }
      }
    ]
  }
};

如果资产重定位加载器不适用于您的原生模块,您可能需要考虑使用 Webpack 的 externals 配置

节点集成

在您的应用代码中启用节点集成

在 Electron 中,您可以使用 BrowserWindow 构造函数选项 在渲染器进程中启用 Node.js。启用以下选项的渲染器将拥有一个类似浏览器的 Web 环境,可以访问 Node.js 的 require 及其所有核心 API。

main.js(主进程)
const win = new BrowserWindow({
  webPreferences: {
    contextIsolation: false,
    nodeIntegration: true
  }
});

这创建了一个独特的环境,需要额外的 Webpack 配置。

在插件配置中设置正确的 Webpack 目标

Webpack 的 目标 对各种 Electron 环境提供了第一类支持。Forge 的 Webpack 插件将根据配置中的 nodeIntegration 选项设置渲染器的编译目标。

  • nodeIntegrationtrue 时,targetelectron-renderer

  • nodeIntegrationfalse 时,targetweb

此选项默认情况下为 false。您可以通过 renderer.nodeIntegration 选项为所有渲染器设置此选项,并且可以在 entryPoints 数组中创建的每个渲染器中覆盖其值。

在下面的配置示例中,Webpack 将为除 media_player 之外的所有入口点编译到 electron-renderer 目标,media_player 将编译到 web 目标。

插件配置
{
  name: '@electron-forge/plugin-webpack',
  config: {
    mainConfig: './webpack.main.config.js',
    renderer: {
      config: './webpack.renderer.config.js',
      nodeIntegration: true, // Implies `target: 'electron-renderer'` for all entry points
      entryPoints: [
        {
          html: './src/app/app.html',
          js: './src/app/app.tsx',
          name: 'app'
        },
        {
          html: './src/mediaPlayer/index.html',
          js: './src/mediaPlayer/index.tsx',
          name: 'media_player',
          nodeIntegration: false // Overrides the default nodeIntegration set above
        }
      ]
    }
  }
}

重要的是,您需要在主进程代码和 Webpack 插件配置中都启用 nodeIntegration。此选项重复是必要的,因为 Webpack 目标在编译时是固定的,但 BrowserWindow 的 Web 首选项是在运行时确定的。

热模块替换

在开发模式下,由于 webpack-dev-server,默认情况下,开发环境中的所有渲染器进程都将启用 热模块替换 (HMR)

但是,HMR 无法在预加载脚本中工作。但是,Webpack 会持续监视和重新编译这些文件,因此重新加载渲染器以获取预加载脚本的更新。

对于主进程,在您启动 electron-forge 的控制台中键入 rs,Forge 将使用新的主进程代码重新启动您的应用。

热重载缓存

使用 Webpack 5 缓存时,需要通过其自身的缓存维护资产权限,并将公共路径注入到构建中。

为了确保这些情况正常工作,请确保在构建中运行 initAssetCache,并使用 options.outputAssetBase 参数。

const relocateLoader = require('@vercel/webpack-asset-relocator-loader');
webpack({
  // ...
  plugins: [
    {
      apply (compiler) {
        compiler.hooks.compilation.tap('webpack-asset-relocator-loader', compilation => {
          relocateLoader.initAssetCache(compilation, outputAssetBase);
        });
      }
    }
  ]
});

React 的热重载

如果您使用 React 组件,您可能希望 HMR 自动获取更改并重新加载组件,而无需手动刷新页面。这可以通过安装 react-hot-loader 来定义哪些模块应该进行热重载来实现。

以下是在 TypeScript 中使用 App 作为 React 组件树中最高级组件的一个使用示例。

import { hot } from "react-hot-loader";

const App: FunctionComponent = () => (
  <div>
    ...
  </div>
);

export default hot(module)(App)

您可以根据需要在任何其他组件中使用此模式。例如,如果您对 AppBar 组件使用 hot() HOC 并对 AppBar 的子组件进行更改,则整个 AppBar 将重新加载,但更高级别的 App 布局保持不变。从本质上讲,更改将传播到组件树中找到的第一个 hot() HOC。

生产环境中会发生什么?

理论上,您不需要关心。在开发环境中,我们启动 webpack-dev-server 实例来为您的渲染器进程提供支持。在生产环境中,我们只需构建静态文件。

假设您使用了我们在上一节中解释的定义的全局变量,那么当您的应用被打包时,一切应该都能正常工作。

如何进行虚拟路由?

如果您想使用类似于react-router 的库在您的应用中进行虚拟路由,您需要确保使用的方法不依赖于浏览器的历史记录 API。浏览器历史记录在开发环境下可以正常工作,但在生产环境下则无法使用,因为您的代码将从文件系统加载,而不是从 Web 服务器加载。在react-router 的情况下,您应该使用MemoryRouter 来使一切正常工作。

上次更新于