簡體   English   中英

在 Electron 渲染器腳本中導入

[英]import inside Electron renderer script

我遵循了 Electron typescript 快速啟動代碼結構。 基本上我克隆了回購協議。 在我想將我的代碼拆分為 multiple.ts 文件並將它們導入渲染器腳本之前,它工作正常。

然后我得到Uncaught ReferenceError: exports is not defined 由於 renderer.ts 頂部的這一行:

import { stuff } from "./otherfile.ts";

挖掘更多信息后,似乎原因是來自 tsconfig 的"module": "commonjs" ...但是如果我將其更改為esnext ,則 Electron 將不再加載預加載腳本!

有沒有人真正設法讓 Electron 和 typescript 充分發揮作用? 我的意思是,能夠跨多個文件使用導入之類的東西?


最小可重現示例:

文件結構:

/dist
/dist/main.js
/dist/preload.js
/dist/renderer.js
/dist/stuff.js
/src
/src/main.ts
/src/preload.ts
/src/renderer.ts
/src/stuff.ts
/index.html

/src/main.ts:

import { ipcMain } from "electron"; // imports work fine in main
...

ipcMain.on(...

/src/preload.ts:

 import { contextBridge, ipcRenderer} from "electron"; // imports work fine in preload

 contextBridge.exposeInMainWorld("my-api", { ....

/src/renderer.ts

 import stuff from "./stuff.ts"; // import fails in renderer (exports is not defined error)

/src/stuff.ts

 const stuff = { ... };
 export default stuff;

/index.html

 <html>
   ...
   <script src="./dist/renderer.js"></script>
 </html>

配置文件:

  • 如果我將“模塊”更改為“es6”,則不會加載 preload.ts。
  • 如果我將“模塊”保留為“commonjs”,則在 renderer.ts 中導入將不起作用

如果我在 ts 編譯文件后在renderer.js中手動添加var exports = {} ,那么我會得到一個不同的錯誤“require is not defined”

長話短說

我相信你的錯誤是由混淆 CommonJS (CJS) 和 ES 模塊 (ESM) 引起的。 聽起來您正在使用 CJS 導出和不兼容的 ESM 導入。 下一節是一個有效的 Election 示例,之后是 CJS 和 ESM 導出(export)和 require(導入)的比較。

工作解決方案

您的 OP 不包含任何代碼,因此這是我對問題所在的最佳猜測,並提供了可行的解決方案。 從設置代碼開始:

# Clone this repository
git clone https://github.com/electron/electron-quick-start-typescript
# Go into the repository
cd electron-quick-start-typescript
# Install dependencies
npm install

接下來創建以下文件:

// src/otherfile.ts
export const stuff = {
    fullOf: 'stuff'
}

export function shareLocation(loc: string): void {
    console.log(`I was imported by: ${loc}`);
}

現在打開src/main.ts文件並進行以下更改:

// Add import to top of file.
import {stuff, shareLocation} from "./otherfile"

// Add this code to the end of the createWindow() function.
shareLocation('main.ts (main.js)');

現在,如果您以npm start運行此示例,您將在終端中看到I was imported by: main.ts (main.js)

如果您嘗試使用src/preload.ts文件執行此操作,您將因沙盒而收到錯誤。 要查看這一點,請在src/preload.ts中進行以下更改:

// Add to first line.
import {stuff, shareLocation} from "./otherfile";

// Add after the for loop inside the DOMContentLoaded function.
shareLocation('preload.ts (preload.js)');

現在,如果運行npm start ,您將在 electron window(網絡瀏覽器)控制台中收到錯誤消息。 這是因為 Electron 中的重要安全設置。對src/main.ts文件進行以下更改:

// Modify the webPreferences of the new BrowserWindow.
const mainWindow = new BrowserWindow({
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
      sandbox: false // <=== ADD THIS LINE
    },
    width: 800,
});

您的導入現在將按預期工作,您應該看到I was imported by: preload.ts (preload.js)在 electron window(網絡瀏覽器)控制台中。 請記住此代碼是不安全的! 您應該使用 IPC而不是禁用沙箱。

如果您仍然遇到錯誤,我相信這是因為您混淆了 CommonJS (CJS) 和 ES 模塊 (ESM)。 聽起來您正在使用 CJS 導出和 ESM 導入。 請參閱下一節演示差異。

CommonJS (CJS) 與 ES 模塊 (ESM)

通用JS(CJS)

CommonJS 是在 Node.js 中導出和要求(導入)模塊的原始方法。 這是 Mozilla 工程師 Kevin Dangoor 在 2009 年推出的一項標准。您可以在一個文件中編寫一些代碼,如下所示:

// example.js
function hello() {
    console.log('World!');
}
module.exports = hello;

然后將代碼要求(導入)到另一個模塊/文件中,如下所示:

// main.js
const hello = require('./example.js');
hello();

就像更新標准的 ES 模塊一樣,“您可以導出functionsvarletconstclasses ”( MDN 文檔)。 這是一個更大的示例,它使用單個導出object導出兩個函數:

// example.js
function hello() {
    console.log('World!');
}
function foo() {
    console.log('Bar!');
}
module.exports = {
    hello,
    foo
};

我們甚至可以更改導出的object以使用不同的名稱來引用我們的函數。 例如,我們可以將導出代碼更改為:

module.exports = {
    H: hello,
    F: foo
};

然后我們會像這樣需要(導入)這段代碼:

// main.js
const {hello, foo} = require('./example.js');
hello();
foo();

// OR with the other export example
const {H, F} = require('./example.js');
H();
F();

ES模塊(ESM)

這是將一個文件/模塊中的 JavaScript 代碼包含到另一個文件/模塊中的現代方法,旨在在現代瀏覽器中工作。 如果您手動為 web 編寫 JavaScript 模塊,則必須將type="module"屬性添加到加載模塊的腳本標記中。

在您的設置和許多其他設置中,您有某種類型的捆綁器(例如 webpack)通過為您編譯/轉譯代碼來為您處理此問題。 不過,這個主題超出了這個問題的 scope,所以讓我們看一個例子。

使用 ESM,我們的簡單示例現在變為:

// example.js
function hello() {
    console.log('World!');
}
export default hello;

請注意我們的導出語句發生了怎樣的變化。 為了簡單起見,我在導出單個項目時提供default導出。 我們還可以內聯導出:

// example.js
export default function hello() {
    console.log('World!');
}

然后我們將代碼導入另一個模塊/文件,如下所示:

// main.js
import hello from "example";
hello();

如果您不想在導出的模塊中使用default ,則必須使用不同的語法來導入代碼。 正如Darryl Noakes提到的,那將是Named Exports 由於這是一個 TypeScript 項目,這包括更改您的 TypeScript 配置。 This SO answer涵蓋了如果你想 go 這個項目的這條路線要做什么。

如果導出多個項目,則不必使用default語句:

// example.js
function hello() {
    console.log('World!');
}
function foo() {
    console.log('Bar!');
}
export {
    hello,
    foo
}

然后您以類似於object destructing的方式導入代碼:

// main.js
import {hello, foo} from "example";
hello();
foo();

對於import方面的額外幫助,此MDN 文檔以及此免費代碼訓練營文章是一個很好的開始。 JavaScript Tutorial 有一個很棒的教程,介紹什么是 object 破壞。 這個SO Q&A更多地涉及模塊導入,包括為什么他們實際上沒有使用 destructing 再次感謝Darryl Noakes指出這一點!

Electron 還不完全支持 ECMAScript 模塊,但這應該不會影響您的 Typescript 代碼,它可以是非常現代的。

選項 1(松散文件)

在您的 index.html 中執行此操作以引導您的渲染器進程的 Typescript 代碼,就像我的這個初始示例一樣。 這將使您快速啟動並運行,盡管它使用節點集成,在您發布到生產環境之前應該禁用它:

<body>
   <div id='root' class='container'></div>

   <script type='text/javascript'>
        require('./built/renderer');
   </script>
</body>

選項 2(捆綁)

要擺脫 require 語句,並更接近 SPA 操作,符合 Electron 安全建議,您可以使用 webpack 捆綁和參考構建 Typescript,如下所示,如我的更新示例所示:

<body>
    <div id='root' class='container'></div>

    <script type='module' src='vendor.bundle.js'></script>
    <script type='module' src='app.bundle.js'></script>
</body>

設置

我在 package.json 中指定了type=commonjs ,在 tsconfig.json 文件中指定了module=commonjs ,而在非 Electron 項目中,我使用 ECMAScript 模塊設置來構建更好的 output。

它的藝術當然是產生一個現代代碼設置,您可以根據自己的喜好進行擴展,而不是局限於啟動項目所做的事情。

抱歉,樣板文件的配置方式是不可能的。

要在renderer進程內部使用import / require您必須降低最新版本 Electron 中的默認安全設置,或者使用轉譯器,以便renderer執行的最終代碼不包含import / require

electron-quick-start-typescript項目中,你會發現這個文件確認了這個問題。

// 這個文件是 index.html 文件所需要的
// 在 window 的渲染器進程中執行。
// 沒有 Node.js API 在此過程中可用,除非
// nodeIntegration 在 webPreferences 中設置為 true。
// 使用 preload.js 有選擇地啟用功能
// 在渲染器進程中需要。

Node.js API 包括importrequire

此外,當您訪問require時,如果啟用了沙箱,那么您實際上獲得了一個 polyfilled 版本,而不是底層require - 請參閱有關預加載腳本沙箱的這些說明

除了更改樣板之外,您唯一的其他選擇是通過預加載腳本訪問您需要的任何內容,例如window.api.yourFunction


您可能希望使用包含通過 Webpack 進行轉譯的樣板,以使您能夠在渲染器進程中使用import

我相信這兩個樣板都可以容納它(如果你被其中之一困住,我可能會建議你一些)


PS - 如果您編寫的代碼旨在分發給其他人,請不要啟用nodeIntegration ,因為它會破壞 Electron 的安全性 model!

PPS - 您也可以嘗試"module": "ES2020" ,這是我們使用的 - 但它不會解決您無法在renderer進程中import / require的問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM