Shopify / sprockets-commoner

Use Babel in Sprockets to compile JavaScript modules for the browser
MIT License
182 stars 22 forks source link

Expose globals for ExecJS #61

Closed perezperret closed 7 years ago

perezperret commented 7 years ago

I pre-render react components using the react-rails gem. I have disabled that gem's sprockets functionality, so it doesn't interfere with sprocket-commoner, however, when I expose globals using 'expose global.SomeComponent' I get ReferenceError: window is not defined if I pre-render the components (otherwise they get mounted on the front-end perfectly), I am guessing this has something to do with how the 'expose' directive generates this globals. If someone can give me some pointers maybe I can figure out how to expose them to the proper global object in ExecJS? Thanks!

bouk commented 7 years ago

Currently commoner does var global = window which won't work in non-browser contexts.

PR welcome

perezperret commented 7 years ago

Ok, I'm trying to step into it, I know I can install a local fork by specifying a path, but I can't seem to make the changes reflect, I ran rake build on my local fork with a different version (0.6.3pr0) and I still get the same javascript (with var global = window), any pointers would be awesome

perezperret commented 7 years ago

I was able to work with the gem, the changes weren't being picked up because the assets were cached by rails, I solved this running rails tmp:clear, but I couldn't get the 'expose' directive to work with the ExecJS server-side rendering.

It looks like the directive attaches the module exports to window or else, and apparently the assignment happens before global is assigned to window. Perhaps if we could make 'expose' attach to the global variable instead of directly to window we could selectively assign this global beforehand, but I need some guidance to understand how this is working.

I know this might not be a priority but it would be super powerful to combine sprockets-commoner's dependency management and bundling with react-rails's server-side rendering, and it would make for a neat setup for using ReactJS with RoR.

To better elaborate, this is what I think is happening (dumbed down):

  window[exposedGlobal.name] = exposedGlobal.value;
  var global = window;

And this is what I think could work (also dumbed down):

  var global = window || global || this;
  global[exposedGlobal.name] = exposedGlobal.value;
bouk commented 7 years ago

It's doing

var global = window;
window[exposedGlobal.name] = exposedGlobal.value;

Right now, but window isn't defined in ExecJS. You can make a PR to replace it with window || this instead

perezperret commented 7 years ago

Yeah that takes care of window not being defined, but ExecJS still can't access the module I'm exposing, ExecJS is very hard to debug so I'm not getting really far, plus I haven't found any info on how to access the global namespace in it. What I know is that a top level this, self, global and window don't work, however, when I use plain JS or CoffeeScript, ExecJS can access the global variables I define there.

From the compiled JS I can see the global variable gets defined inside a function passed to __commoner_initialize_module__:

  var $module$identifier = __commoner_initialize_module__(function (module, exports) {
    ...
    exports.default = SomeComponent;
    global.SomeComponent = exports['default'] != null ? exports['default'] : exports;
  })

It looks as if this function gets called in the browser and not in ExecJS, maybe this shines a light on it for you, I haven't been able to figure it out.

perezperret commented 7 years ago

I finally got this to work, but it's still a bit quirky, I had to modify bundle.rb to make global a global variable (declaring it without var), otherwise the browser can't find it (weirdly ExecJS can), then in my app, I expose what I need to that global variable (i.e.: 'expose global.ExposedObject'). On the other hand I had to add a check in bundle.rb to make global = this if window is undefined.

So I have everything working like I needed, but as I said it's still a bit quirky, maybe @bouk can shed a little light on to why this behaviour is happening. You can look at my changes in my fork. And a little gist that sort of explains what I need this for.

bouk commented 7 years ago

@perezperret I have pushed version 0.6.4 to rubygems with the fix