在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"); }
有两件事我要先指出:
- 在web worker中包含节点模块
为了包含模块ivona-node
我不得不改变它的一些代码。 当我尝试浏览它时,我得到一个错误: Uncaught Error: Cannot find module '/node_modules/ivona-node/src/proxy'
。 检查生成的bundle.js
我注意到它不包含ivona-node
的src
文件夹中的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
的数据,这让我不得不面对第二个问题。
- 从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
提供的相同方式。 这些是您可以遵循的步骤:
-
安装
browserify
npm install -g browserify
-
安装
ivona-node
npm install ivona-node --save
-
去
node_modules/ivona-node/src/main.js
并改变这一行:HttpsPA = require(__dirname + '/proxy');
这样:
HttpsPA = require('./proxy');
-
创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
-
放在一起
修改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>