ruby-on-railswebpackopentok

Add opentok-layout-js to webpack for Rails


I'm trying to add the opentok-layout-js to a rails 6 app that uses webpack. (https://github.com/aullman/opentok-layout-js)

I see that i can add it thru yarn. so I've done that. In my app/javascript/packs/application.js file i added:

require("opentok-layout-js")

When i try to run the example:

<script type="text/javascript" charset="utf-8">
    var layoutContainer = document.getElementById("layoutContainer");

    // Initialize the layout container and get a reference to the layout method
    var layout = initLayoutContainer(layoutContainer).layout;

    // Below is a normal hello world OpenTok application for v2 of the API
    // The layout container will redraw when the layout mtehod is called and
    // adjust the layout accordingly
    var sessionId = "mySessionId";
    var token = "myToken";
    var apiKey = "myAPIKey";

    var session = OT.initSession(sessionId);
    session.on("streamCreated", function(event){
        session.subscribe(event.stream, "layoutContainer", {
            insertMode: "append"
        });
        layout();
    }).connect(apiKey, token, function (err) {
        if (!err) {
            session.publish("publisherContainer");
            layout();
        }
    });
</script>

This keeps yielding an "initLayoutContainer" is not defined message.

In their example they use:

<script src="js/opentok-layout.min.js"></script>

to initialize that script. where as I use webpack and have it included in my pack. What am i doing wrong and why can't the initLayoutContainer be seen when its done my way? My pack is included in the header of the page and works for all other purposes.


Solution

  • Webpack does not export anything to the global scope by default. To handle the issue you describe, you have a few options:

    1. Put your JS in Webpack (this is what I recommend)

      Instead of using a <script> tag to initialize and setup opentok, just add a JS file to your Webpack dependency graph that does the same thing. Here you get the benefits of bundling this JS, ensuring modules are available with import statements, linting, unit testing, etc.

      // app/javascript/src/setup.js 
      import initLayoutContainer from 'opentok-layout-js'
      
      document.addEventListener('DOMContentLoaded', function() {
        const layoutContainer = document.getElementById("layoutContainer")
        const layout = initLayoutContainer(layoutContainer)
        // ...
      })
      
      // app/javascript/packs/application.js
      
      import '../src/setup'
      
    2. OR, Configure Webpack to expose initLayoutContainer to the global scope with expose-loader

      If you insist on having setup live outside your Webpack bundle in a <script> tag, you can extend your Webpacker configuration to ensure an export of your choosing is made available on the window object in the browser when imported within your dependency graph.

      // config/webpack/environment.js
      
      const { environment } = require('@rails/webpacker')
      
      environment.loaders.append('opentok-layout', {
        test: require.resolve('opentok-layout-js'),
        use: [{
          loader: 'expose-loader',
          options: 'initLayoutContainer'
        }]
      })
      
      module.exports = environment
      
      // app/javascript/packs/application.js
      
      import 'opentok-layout-js'
      
    3. OR, Manually expose initLayoutContainer to the global scope the first time you import

      An alternative to 2. is simply attach the imported function to the window yourself from within Webpack. This is my least favorite, because I prefer to avoid side effects like this and I find it to be error prone; but, most folks find this easier.

      // app/javascript/packs/application.js
      
      import initLayoutContainer from 'opentok-layout-js'
      window.initLayoutContainer = initLayoutContainer