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

Can't Require Node Modules In WebWorker (NWJS)

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

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

注意:我认为 native_fs_ 的方法不能像它应该的那样将 mp3 以块(流)的形式写入磁盘,而且我在 Ivona 包中也遇到了错误(实际上首先)我不知道如何解决。我包含了所有信息以重现此内容。

这是我在控制台中遇到的错误:Uncaught SyntaxError: Unexpected token var VM39 ivonabundle.js:23132

npm 安装 ivona-node

npm 安装 browserify-fs

npm install -g browserify

浏览器化 main.js > ivonabundle.js

浏览器化 index.js > fsbundle.js


package.json...

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

index.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.worker.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 worker 中包含节点模块

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

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 和行为,但某些功能不可用,因为浏览器不要对请求给予几乎一样多的控制。非常重要的是 class http.ClientRequest 的事实,这个 nodejs 环境中的 class 创建一个 OutgoingMessage 产生这个语句 Stream.call(this) 允许使用请求中的方法 pipe,但在 browserify 版本中调用 https.request 结果是 Writable 流,这是 [=42= 中的调用]:stream.Writable.call(self)。所以即使使用这种方法,我们也有明确的 WritableStream

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

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

  1. 从网络工作者创建文件

众所周知,从 Web 应用程序访问文件系统有很多安全问题,所以问题是我们如何从 Web Worker 访问文件系统。第一种方法是使用 HTML5 FileSystem API. This approach has the inconvenient that it operate in a sandbox, so if we have in a desktop app we want to have access to the OS FileSystem. To accomplish this goal we can pass the data from the web worker to the main thread where we can use all the nodejs FileSystem functionalities. Web worker provide a functionality called Transferable Objects, you can get more info here and here ,我们可以使用它将从 web worker 中的模块 ivona-node 接收到的数据传递给主线程,然后在中使用 require('fs')node-webkit 为我们提供的方式相同。这些是您可以遵循的步骤:

  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 仅用于访问 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]);
    });

}

和index.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>