将变量添加到Browserify定义的外部闭包中?

时间:2015-07-15 23:12:47

标签: closures browserify

Browserify是否提供了将变量添加到它生成的最顶层闭包的方法?

我想定义一个全局的变量,但我的所有模块都可以看到。 "使用的标准答案要求(......)"在我的情况下不起作用,因为变量的值在处理依赖关系时尚未准备就绪。我真的需要一种方法来在闭包中声明一个包含我所有模块的变量。

这是一个非常简化的例子,说明如果我没有使用Browserify并且我将所有模块放在一个文件中会是什么样子。我试图弄清楚如何在Browserify世界中定义类似myVar的内容,我不会在其中定义外部范围:

(function() {
    var myVar; <-- This is what I want in the Browserify world. Non-global, outside the individual module closure.

    var ModuleA = function() {
        function a() {
            console.log(myVar);
        };
        return { a: a };
    }();

    var ModuleB = function() {
        function b() {
            console.log(myVar);
        };
        return { b: b };
    }();

    function doItToIt() {
        // Psuedo-code... assume I'm loading myVar asynchronously.
        ajaxRequest(function(result) {
            myVar = result;
            ModuleA.a();
            ModuleB.b();
        });
    }

    doItToIt();

})();

1 个答案:

答案 0 :(得分:2)

有关转换方法的详细信息,请参阅下面的编辑

抱歉迟到了。

有很多方法可以做到这一点。我不确定你要完成什么,因为你没有说你的代码试图做什么。以下是一些方法,按“最佳选择优先”排序:

  1. 使用全局。我知道你说“非全球”,但你没有说明原因,所以也许这仍然是一个选择?当你创建“var myVar”时你没有分配价值。我假设该值仅在“doItToIt”中分配,那么为什么不使用全局? (global||window)['myVar'] = result;要小心全球名称冲突。
  2. 设置browserify转换。转换工作在每个所需文件的CONTENTS上。作为文件的内容被转储到browserify的包装器中,这是添加变量定义的理想位置。
  3. // compile.js

    var fs = require('fs'),
    stream = require('stream'),
    browserify = require('browserify'),
    b = browserify({
        basedir: './'
    });
    
    //Setup browserify transform, see NodeJS Stream transforms:
    //https://nodejs.org/api/stream.html#stream_class_stream_transform
    b.transform(function(file, opts) {
        function MyTransform(opts) {
            //Create buffer array
            this.streamParts = [];
    
            //Call Transform constructor on this class
            stream.Transform.bind(this, opts)();
        }
    
        //Assign stream.Transform as parent prototype
        MyTransform.prototype = Object.create(stream.Transform.prototype);
    
        //Override _transform function to collect all chunks and store in our array
        MyTransform.prototype._transform = function(data, encoding, done) {
            //Store this chunk
            this.streamParts.push(data);
            done();
        };
    
        //On completion, merge (concat) chunks and turn into a string
        MyTransform.prototype._flush = function(done) {
            //Concat all chunks into a single buffer
            var buf = Buffer.concat(this.streamParts);
    
            //Turn buffer into a utf8 string
            var src = buf.toString('utf8');
    
            //Prefix with 'var myVar;' and push out the source body contents
            this.push('var myVar;' + src);
            done();
        };
    
        //Create and return our transform
        return new MyTransform();
    }, {
        global: true //Warning: This will transform EVERY required file
    });
    
    //Require (and transform)
    b.require('./main.js');
    
    //Pipe to stdout... feel free to pipe to a file instead
    b.bundle().pipe(process.stdout);
    

    现在通过:nodejs compile.js

    运行
    1. (MacOSX,Linux)通过sed或awk管道browserify的输出。如果任何其他代码具有这个确切的函数定义,这可能会失败...虽然我怀疑其他代码会关心是否声明了未使用的变量“myVar”:
    2. browserify main.js | sed -e 's/function(require,module,exports){/(function(require,module,exports){var myVar;/g'

      修改

      为了回应你的评论,我做了这个快速简单的装载机功能。使用此匿名函数包装browserify包:

      (function() {
          var resources = {}, testCallbacks = [];
      
          //This function loops through all "test" functions and calls
          //them. If the test function returns "true", than the callback
          //has been fired, and can be removed from the test list,
          //otherwise all resources for that callback are not ready
          //and will need to be tested again upon the load of the next
          //resource
          function fireReadyCallbacks() {
              var remainingTests = [];
              for (var i = 0, len = testCallbacks.length; i < len; i++) {
                  var cb = testCallbacks[i];
                  if (cb() !== true)
                      remainingTests.push(cb);
              }
              testCallbacks = remainingTests;
          }
      
          //Our fancy require function
          function smartRequire(urls, readyCallback) {
              function insertScript(url) {
                  //If resource is currently loading/loaded, just return
                  if (resources[url])
                      return;
      
                  //Add to 'global' resource list
                  var res = resources[url] = {
                      url: url,
                      loaded: false
                  };
      
                  //Insert script into page
                  var scr = document.createElement('script');
                  var eventName = (typeof scr.onload !== 'undefined') ? 'load' : 'readystatechange';
                  scr.addEventListener(eventName, function(e) {
                      //If IE says it isn't ready, return
                      if (eventName === 'readystatechange' && e.readyState !== 'complete')
                          return;
      
                      //TODO: test for error
                      //Mark resource as being fully loaded
                      res.loaded = true;
      
                      //Check if any callbacks are ready to fire
                      fireReadyCallbacks();
                  });
      
                  //Set async to true, and src to the url
                  scr.setAttribute('async', 'true');
                  scr.setAttribute('src', url);
      
                  //Insert into document
                  document.head.appendChild(scr);
              }
      
              //Setup a test callback and add to testCallbacks array.
              //This function calls the specified callback ONLY
              //if all resources defined have been loaded
              testCallbacks.push(function() {
                  //Have all specified urls been successfully loaded?
                  for (var i = 0, len = urls.length; i < len; i++) {
                      var url = urls[i];
                      var res = resources[url];
                      if (!res || !res.loaded)
                          return false; //Nope!
                  }
      
                  //Call the callback
                  readyCallback();
      
                  //Inform fireReadyCallbacks that this callback is not needed anymore 
                  return true;
              });
      
              //Add all resources to "resources" and insert all scripts into DOM
              for (var i = 0, len = urls.length; i < len; i++)
                  insertScript(urls[i]);
      
              //Test if we are ready to fire callback.
              //This can happen if all resources are already loaded.
              fireReadyCallbacks();
          }
      
          (function(){
              /*Browserify bundle output goes here*/
          })();
      })();
      

      然后,您可以设置browserify转换以包装所需的模块,如下所示:

      smartRequire(['path/to/jquery','whatever/else/is/required'], function(){/*Place module output from browserify transform here*/});
      

      以下是完成上述操作的完整代码:

      // compile.js

      var fs = require('fs'),
              stream = require('stream'),
              browserify = require('browserify'),
              b = browserify(['main.js'], {
                  basedir: './'
              });
      
      //Setup browserify transform, see NodeJS Stream transforms.
      //https://nodejs.org/api/stream.html#stream_class_stream_transform
      b.transform(function(file, opts) {
          function MyTransform(opts) {
              //Create buffer array
              this.streamParts = [];
      
              //Call Transform constructor on this class
              stream.Transform.bind(this, opts)();
          }
      
          //Assign stream.Transform as parent prototype
          MyTransform.prototype = Object.create(stream.Transform.prototype);
      
          //Override _transform function to collect all chunks and store in our array
          MyTransform.prototype._transform = function(data, encoding, done) {
              //Store this chunk
              this.streamParts.push(data);
              done();
          };
      
          //On completion, merge (concat) chunks and turn into a string
          MyTransform.prototype._flush = function(done) {
              //Concat all chunks into a single buffer
              var buf = Buffer.concat(this.streamParts);
      
              //Turn buffer into a utf8 string
              var src = buf.toString('utf8');
      
              //Wrap with our smartRequire... change resources as needed, can even
              //place resources into a variable
              this.push('smartRequire(["http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"],function(){' + src + '});');
              done();
          };
      
          //Create and return our transform
          return new MyTransform();
      }, {
          global: true //Warning: This will transform EVERY required file
      });
      
      var outFile = 'bundle.js';
      console.log('Writing ' + outFile + '...');
      
      var outputStream = fs.createWriteStream(outFile, {
          flags: 'w',
          encoding: 'utf8'
      });
      
      //Get the output stream from browserify
      var dataStream = b.bundle(), firstChunk = true;
      
      //Our smart require blob... minified for sanity's sake
      var prefixBlob='function fireReadyCallbacks(){for(var c=[],e=0,f=testCallbacks.length;e<f;e++){var a=testCallbacks[e];!0!==a()&&c.push(a)}testCallbacks=c}function smartRequire(c,e){function f(b){if(!resources[b]){var c=resources[b]={url:b,loaded:!1},d=document.createElement("script"),a="undefined"!==typeof d.onload?"load":"readystatechange";d.addEventListener(a,function(b){if("readystatechange"!==a||"complete"===b.readyState)c.loaded=!0,fireReadyCallbacks()});d.setAttribute("async","true");d.setAttribute("src",b);document.head.appendChild(d)}}testCallbacks.push(function(){for(var b=0,a=c.length;b<a;b++){var d=resources[c[b]];if(!d||!d.loaded)return!1}e();return!0});for(var a=0,g=c.length;a<g;a++)f(c[a]);fireReadyCallbacks()};var resources={},testCallbacks=[];';
      
      //Intercept the stream, and inject our prefix code
      //before the first chunk
      dataStream.on('data', function(chunk) {
          if (firstChunk) {
              outputStream.write('(function(){' + prefixBlob);
              outputStream.write(chunk);
              firstChunk = false
          } else {
              outputStream.write(chunk);
          }
      });
      dataStream.on('end', function(chunk) {
          //Add end of closure wrapper
          outputStream.end((chunk) ? (chunk.toString('utf8') + '})();') : '})();');
      });
      dataStream.on('error', function(file, err) {
          console.log('Error: ', file, err);
      });
      
      outputStream.on('finish', function() {
          outputStream.close();
          console.log('Compiled Success!');
      });