deathcap / nodeachrome

run Node.js scripts in Google Chrome (experimental)
MIT License
2 stars 0 forks source link

Remove browserify concatenation bundling? #27

Open deathcap opened 8 years ago

deathcap commented 8 years ago

https://github.com/substack/browserify-handbook#pack concatenates all of the browserified modules into one file for use in the browser. This makes sense when served over the web as browserify originally intended (reduce network latency from multiple round trips), but the rationale may not be as valid for Chrome extensions like Nodeachrome where the HTML and JavaScript files are served locally from disk.

deathcap commented 8 years ago

Splitting the bundle itself should be no problem, there is even a module to do it factor-bundle: https://github.com/substack/browserify-handbook#factor-bundle - but the problem is then how to load each file. HTML5 script tag can load from anywhere, may be able to tell when it is loaded by onreadystatechange and/or onload/onerror, and it is synchronous when inserted in the page except when the async attribute is set, but unable to directly pass a return value back from the script to its "caller", to match the semantics of require(). XMLHttpRequest/fetch to read raw JavaScript files then eval() them would work, but is even uglier, and eval() is not allowed in Chrome extensions except in sandboxes, anyways.

browserify wraps each module in a function, providing the require, module, and exports globals (and maybe others like global, process, Buffer, dirname, filename), example:

$ echo 'require("asarray")' | ./node_modules/.bin/browserify  -
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
require("asarray")

},{"asarray":2}],2:[function(require,module,exports){
'use strict';

module.exports = function(o) {
  var length = o.length;
  var a = Array(length);

  for (var i = 0; i < length; i += 1) {
    a[i] = o[i];
  }

  return a;
}

},{}]},{},[1]);

maybe do something similar, but execute it, (function(require,module,exports){...})(...), and assign a global object to module.exports, keyed on the unique filename path, for require() to lookup.

But as far as I can tell, some sort of compilation/preprocessing step will always be required, since the HTML script tag executes code in the global context. Unless ES6 modules and/or the module loading API is implemented? Or could it be possible to exploit the fact that the script tags execute synchronously, to overwrite and capture the globals? <script src=loader-start.js></script> <script src=module1.js></script> <script src=loader-end.js></script>? where loader-start sets require,module,exports appropriately, and loader-end saves it and restores the globals on Object.keys(window), as needed. Perhaps that's too crazy.


update: <script src=>'ing unmodified node_modules files directly is problematic because <script> runs in the global context. NPM modules can define globals using function foo(), and these cannot be deleted, per the language spec - you can reassign them, but then the calling functions will not be able to execute them! It is a real problem. Browserify wraps all modules in an IIFE for this reason, creating a new scope to avoid polluting global. https://github.com/deathcap/cjs4web