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

Posted

技术标签:

【中文标题】在 WebWorker (NWJS) 中不能需要节点模块【英文标题】:Can't Require Node Modules In WebWorker (NWJS) 【发布时间】:2016-06-01 23:06:45 【问题描述】:

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

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

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

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

在 NWJS 中重现的步骤:

npm install ivona-node

npm install browserify-fs

npm install -g browserify

现在我对 ivona-node 的 main.js 和 browserify-fs 的 index.js 进行了浏览:

浏览 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");

【问题讨论】:

你必须把它保存到磁盘吗?如果您真的需要,您可以将工作人员的 bin 数据发送到顶部以保存,但我不明白为什么它需要保存。 也许吧,但是我从 ivona-node 得到的错误呢?我没有让他们进入 dom,只是在 webworker 中。 Uncaught SyntaxError: Unexpected token var VM39 ivonabundle.js:23132 为什么不使用require('child_process').fork 我不想加载一个新的 nwjs 实例来做这件事。这将是非常昂贵的资源。 【参考方案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');。之后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环境中创建一个OutgoingMessage,它产生这个语句Stream.call(this)允许在请求中使用pipe方法,但是在browserify调用https.request 时的版本是Writable 流,这是ClientRequest 内部的调用:stream.Writable.call(self)。因此,即使使用这种方法,我们也有明确的WritableStream

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

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

    从网络工作者创建文件

众所周知,从 Web 应用程序访问 FileSystem 存在许多安全问题,所以问题是我们如何才能从 Web Worker 访问 FileSystem。第一种方法是使用 HTML5 FileSystem API。这种方法的不便之处在于它在沙箱中运行,因此如果我们在桌面应用程序中,我们希望能够访问 OS 文件系统。为了实现这个目标,我们可以将数据从 web worker 传递到主线程,在那里我们可以使用所有nodejs FileSystem 功能。 Web Worker 提供了一个名为Transferable Objects 的功能,您可以获得更多信息here 和here,我们可以使用这些信息将从Web Worker 中的模块ivona-node 接收到的数据传递给主线程,然后使用@987654360 @ 与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');

    创建您的bundle.js

    在这里你有一些选择,创建一个bundle.js 以允许一个require() 或将一些代码放在一个文件中,其中包含一些你想要的逻辑(你实际上可以包含 web worker 的所有代码),然后创建bundle.js。在此示例中,我将创建 bundle.js 仅用于访问 require() 并在 Web Worker 文件中使用 importScripts()

    browserify -r ivona-node &gt; 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>

【讨论】:

以上是关于在 WebWorker (NWJS) 中不能需要节点模块的主要内容,如果未能解决你的问题,请参考以下文章

html5 的web worker中不能用jq

nwjs-nodejs- 加密和解密 img 文件 (jpg) 并将解密后的数据用于 img 元素

Web Worker 和 Server-Sent Events

nwjs启动时候会有一会白屏,怎么去掉

node-webkit,nwjs 系统托盘Tray实践

node-webkit,nwjs 系统托盘Tray实践