不能在 WebWorker (NWJS) 中要求节点模块

Can't Require Node Modules In WebWorker (NWJS)

本文关键字:节点 模块 WebWorker NWJS 不能      更新时间:2023-09-26

我正在尝试做一些我认为很简单的事情。我正在使用nwjs(以前称为Node-Webkit),如果您不知道,这基本上意味着我正在使用Chromium和Node开发桌面应用程序,其中DOM与Node处于同一范围内。我想将工作卸载给网络工作者,以便在我将一些文本发送到 Ivona Cloud(使用 ivona-node)时 GUI 不会挂起,这是一个文本到语音 API。音频在生成时以块的形式返回并写入 MP3。Ivona-node使用FS将MP3写入驱动器。我让它在 dom 中工作,但需要网络工作者不要挂起 UI。所以我有两个节点模块需要在 webworker 中使用,ivona-node 和 fs。

问题是在 Web 工作者中你不能使用 require。所以我尝试用browserify打包ivona-node和fs(我使用了一个名为browserify-fs的软件包)并将require替换为importScripts()。现在我在节点模块中收到 var 错误。

注意:我认为native_fs_的方法不适用于将 mp3 以块(流)的形式写入磁盘,而且我在 Ivona 包中也遇到了错误(实际上首先是)我不知道如何修复。我正在包括所有信息来重现这一点。

这是我在控制台中遇到的一个错误:未捕获的语法错误:意外的令牌变量 VM39 ivonabundle.js:23132

  • 在 NWJS 中重现的步骤:

npm install ivona-node

npm install browserify-fs

npm install -g browserify

  • 现在我浏览器化了main.js用于ivona-node和index.js用于browserify-fs:

Browserify main.js> ivonabundle.js

browserify index.js> fsbundle.js


包.json...

{
  "name": "appname",
  "description": "appdescr",
  "title": "apptitle",
  "main": "index.html",
  "window":
  {
    "toolbar": true,
    "resizable": false,
    "width": 800,
    "height": 500
  },
  "webkit":
  {
    "plugin": true
  }
}

索引.html...

<html>
<head>
    <title>apptitle</title>
</head>
<body>
<p><output id="result"></output></p>
<button onclick="startWorker()">Start Worker</button>
<button onclick="stopWorker()">Stop Worker</button>
<br><br>
<script>
    var w;
    function startWorker() {
        if(typeof(Worker) !== "undefined") {
            if(typeof(w) == "undefined") {
                w = new Worker("TTMP3.worker.js");
                w.postMessage(['This is some text to speak.']);
            }
            w.onmessage = function(event) {
                document.getElementById("result").innerHTML = event.data;
            };
        } else {
            document.getElementById("result").innerHTML = "Sorry! No Web Worker support.";
        }
    }
    function stopWorker() {
        w.terminate();
        w = undefined;
    }
</script>
</body>
</html>

TTMP3.工人.js...

importScripts('node_modules/browserify-fs/fsbundle.js','node_modules/ivona-node/src/ivonabundle.js');
onmessage = function T2MP3(Text2Speak)
{
postMessage(Text2Speak.data[0]);
//var fs = require('fs'),
//    Ivona = require('ivona-node');
var ivona = new Ivona({
    accessKey: 'xxxxxxxxxxx',
    secretKey: 'xxxxxxxxxxx'
});
//ivona.listVoices()
//.on('end', function(voices) {
//console.log(voices);
//});
//  ivona.createVoice(text, config)
//  [string] text - the text to be spoken
//  [object] config (optional) - override Ivona request via 'body' value
ivona.createVoice(Text2Speak.data[0], {
    body: {
        voice: {
            name: 'Salli',
            language: 'en-US',
            gender: 'Female'
        }
    }
}).pipe(fs.createWriteStream('text.mp3'));
postMessage("Done");
}

我想先指出两件事:

  1. 在 Web 工作线程中包含节点模块

为了包含模块ivona-node我不得不稍微更改其代码。当我尝试将其浏览器化时,出现错误:Uncaught Error: Cannot find module '/node_modules/ivona-node/src/proxy'。检查生成的bundle.js,我注意到它不包括模块proxy的代码,该代码位于ivona-node src文件夹中的文件proxy.js中。我可以通过以下方式加载proxy模块更改此行HttpsPA = require(__dirname + '/proxy');HttpsPA = require('./proxy'); .之后,ivona-node可以通过browserify加载到客户端。然后,在尝试遵循该示例时,我遇到了另一个错误。原来这段代码:

ivona.createVoice(Text2Speak.data[0], {
    body: {
        voice: {
            name: 'Salli',
            language: 'en-US',
            gender: 'Female'
        }
    }
}).pipe(fs.createWriteStream('text.mp3'));

不再正确,它会导致错误: Uncaught Error: Cannot pipe. Not readable. 这里的问题出在模块http . 该模块browserify包装了许多内置的npm模块,这意味着当您使用require()或使用其功能时,它们可用。 http 就是其中之一,但正如您可以在此处参考的那样:strem-http,它试图尽可能匹配节点的 API 和行为,但某些功能不可用,因为浏览器无法对请求进行尽可能多的控制。非常重要的是类http.ClientRequest的事实,这个类nodejs环境中创建一个生成此语句的OutgoingMessageStream.call(this)允许在请求中使用方法pipe,但是在browserify版本中,当您调用https.request时,结果是Writable Stream,这是ClientRequest内的调用: stream.Writable.call(self) .因此,即使使用此方法,我们也明确地WritableStream

Writable.prototype.pipe = function() {
  this.emit('error', new Error('Cannot pipe. Not readable.'));
}; 

上述错误的责任人。现在我们必须使用不同的方法来保存ivona-node中的数据,这让我进入第二个问题。

  1. 从 Web 辅助角色创建文件

众所周知,从Web应用程序访问文件系统存在许多安全问题,因此问题在于我们如何从Web工作者访问文件系统。第一种方法是使用 HTML5 文件系统 API。这种方法在沙箱中运行的不便之处,因此,如果我们在桌面应用程序中,我们希望访问操作系统文件系统。为了实现这一目标,我们可以将数据从 Web worker 传递到主线程,在那里我们可以使用所有nodejs文件系统功能。 Web worker 提供一个名为 Transferable Objects ,您可以在此处和此处获取更多信息,我们可以使用这些信息将从 Web worker 中的模块ivona-node接收的数据传递到主线程,然后以与node-webkit相同的方式使用require('fs')提供给我们。 您可以按照以下步骤操作:

  1. 安装browserify

    npm install -g browserify
    
  2. 安装ivona-node

    npm install ivona-node --save
    
  3. 转到node_modules/ivona-node/src/main.js并更改此行:

    HttpsPA = require(__dirname + '/proxy');

    通过这个:

    HttpsPA = require('./proxy');

  4. 创建您的bundle.js .

    在这里,您有一些替代方案,创建一个bundle.js以允许require()或将一些代码放入具有所需逻辑的文件中(您实际上可以包含 Web worker 的所有代码),然后创建bundle.js。在此示例中,我将仅创建bundle.js以访问 Web worker 文件中的require()和使用importScripts()

    browserify -r ivona-node > ibundle.js

  5. 把所有放在一起

    修改 Web worker

    index.html 的代码,以便在 Web worker 中接收数据并将其发送到主线程(在 index.html 中)

这是 Web worker 的代码 (MyWorker.js)

importScripts('ibundle.js');
var Ivona = require('ivona-node');
onmessage = function T2MP3(Text2Speak)
{
    var ivona = new Ivona({
        accessKey: 'xxxxxxxxxxxx',
        secretKey: 'xxxxxxxxxxxx'
    });
    var req = ivona.createVoice(Text2Speak.data[0], {
        body: {
            voice: {
                name: 'Salli',
                language: 'en-US',
                gender: 'Female'
            }
        }
    });
    req.on('data', function(chunk){
        var arr = new Uint8Array(chunk);
        postMessage({event: 'data', data: arr}, [arr.buffer]);
    });
    req.on('end', function(){
        postMessage(Text2Speak.data[0]);
    });
}

和索引.html:

<html>
<head>
    <title>apptitle</title>
</head>
<body>
<p><output id="result"></output></p>
<button onclick="startWorker()">Start Worker</button>
<button onclick="stopWorker()">Stop Worker</button>
<br><br>
<script>
    var w;
    var fs = require('fs');
    function startWorker() {
        var writer = fs.createWriteStream('text.mp3');
        if(typeof(Worker) !== "undefined") {
            if(typeof(w) == "undefined") {
                w = new Worker("MyWorker.js");
                w.postMessage(['This is some text to speak.']);
            }
            w.onmessage = function(event) {
                var data = event.data;
                if(data.event !== undefined && data.event == 'data'){
                     var buffer = new Buffer(data.data);
                     writer.write(buffer);
                }
                else{
                    writer.end();
                    document.getElementById("result").innerHTML = data;
                }
            };
        } else {
            document.getElementById("result").innerHTML = "Sorry! No Web Worker support.";
        }
    }
    function stopWorker() {
        w.terminate();
        w = undefined;
    }
</script>
</body>
</html>