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

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

问题是在webworker中你不能使用require。 所以我试着用browserify打包ivona-node和fs(这里有一个名为browserify-fs的包,我用这个包),用importScripts()replacerequire。 现在我得到节点模块中的var错误。

注意:我不认为native_fs_的方法会将mp3写入磁盘(stream),因为它应该是这样,我也得到了Ivona包中的错误(实际上是最重要的)不知道如何解决。 我包括所有信息来重现这一点。

这是我在控制台中得到的错误:未捕获的SyntaxError:意外的令牌var VM39 ivonabundle.js:23132

  • 在NWJS中重现的步骤:

npm安装ivona-node

npm安装browserify-fs

npm install -g browserify

  • 现在,我浏览了针对browserify-fs的ivona-node和index.js的main.js:

browserify main.js> ivonabundle.js

browserify 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文件夹中的ivona-node文件中的模块proxy的代码。 我可以加载proxy模块,改变这一行HttpsPA = require(__dirname + '/proxy'); 由此: HttpsPA = require('./proxy'); 。 之后, ivona-node可以通过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. Uncaught Error: Cannot pipe. Not readable. 这里的问题是在模块http 。 模块browserify已经包装了npm许多内置模块,这意味着当你使用require()或者使用它们的function时它们是可用的。 http是其中之一,但正如你可以在这里引用: strem-http ,它试图尽可能地匹配节点的api和行为,但是有些function是不可用的,因为浏览器不能提供太多的控制请求。 非常显着的是类http.ClientRequest的事实,这个类在nodejs环境中创build了一个OutgoingMessage ,它产生了这个语句Stream.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. 从networking工作者创build一个文件

众所周知,从Web应用程序访问FileSystem有许多安全问题,所以问题是我们如何从Web Worker访问FileSystem。 第一种方法是使用HTML5 FileSystem API 。 这种方法不方便,它在沙箱中运行,所以如果我们在桌面应用程序中,我们想要访问操作系统文件系统。 为了实现这个目标,我们可以将数据从web worker传递到主线程,在那里我们可以使用所有的nodejs FileSystemfunction。 Web工作者提供了一个叫做Transferable Objects的function,你可以在这里和这里获得更多的信息,我们可以使用这个function将从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. 创build你的bundle.js

    在这里你有一些select,创build一个bundle.js来允许require()或者把一些代码放在一个你想要的逻辑(你实际上可以包含web worker的所有代码)的文件中,然后创build这个bundle.js 。 在这个例子中,我将创buildbundle.js仅用于访问require()并在web worker文件中使用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>