Making an es6 class available in an eex template (<script> tag)

Can’t seem to figure this one out. I can import the class, webpack compiles it (i can see it in priv/static/js/app.js). But I get a ReferenceError: Thinger is not defined when calling const thinger = new Thinger() in the script tag.

Here’s the relevant code, there’s not much to it right now:

assets/js/app.js

import Thinger from "./thinger/thinger"

assets/js/thinger/thinger.js

export default class Thinger {

  constructor() {
    this.foo = "bar";
  }

  // ... do stuff
}

lib/web/templates/thing/show.html.eex

<script>
  window.onload = function () {
    const thinger = new Thinger();
    thinger.doStuff();
  }
</script>

I ordered the programming phoenix 1.4 ebook and they are using an object for their js stuff, which I would rather not do. I looked around at a ton of non-phoenix webpack solutions to this problem, since this is not really a phoenix issue most likely. But they just got me into different, but still broken states. A couple examples:

  • Export Thinger globally with a const Thinger = require('./thinger/thinger').default; module.exports = Thinger;' + adding a library and libraryTarget to my webpack config (output object)
  • Checked to see if Thinger was attached to the window or document elements
  • Messed around with the class invocation (default, no default, default with no class name, etc.)

Hopefully someone has done this before and can show me the way!

Webpack version: 4.4.0
Phoenix version: 1.4.9

Welcome to the forum!

The problem is that the code inside the bundle created by webpack is (deliberately) completely isolated. So Thinger isn’t available via the Global object.

Have a look at:

If you put:

  window.onload = function () {
    const thinger = new Thinger();
    thinger.doStuff();
  }

at the end of app.js it should work.

This is equivalent to:
https://media.pragprog.com/titles/phoenix14/code/watching_videos/listings/rumbl/assets/js/app.change1.js

where

import Player from "./player"
let video = document.getElementById("video")

if(video) {
  Player.init(video.id, video.getAttribute("data-player-id"), () => {
    console.log("player ready!")
  })
}

is appended to the end of app.js. That code is only run once after the bundle is loaded.


You can create separate bundles for each page with varying initialization logic with multiple entry points in the webpack configuration:


The load event is fired when the whole page has loaded, including all dependent resources such as stylesheets images.

The DOMContentLoaded event fires when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading.

A different event, load , should be used only to detect a fully-loaded page. It is a common mistake to use load where DOMContentLoaded would be more appropriate.

3 Likes

Awesome. You solved not only my initial problem, but my next problem as well (per-page bundles). I had that exact page opened from the book and didn’t read it carefully enough, obviously. The answer was right in front of me the whole time. Thank you @peerreynders!

4 Likes