How to import/use Javascript in components, not app.js

Hey everyone,

im using Pheonix LiveView for a WebApp that relies on reading/writing the users clipboard.
Thus i rely on some regular Javascript to be executed. At the moment i have a fairly large <script> tag in my component that contains all this code.
While developing some new features, i needed this Javascript code in two different compoents, also i wanted to trim down my component and outsource all the Javascript contained in the <script> tag there.

Using Javascript only, this is a fairly simple task, but using Phoenix/LiveView it’s unclear for me how to accomplish this. First attemp was to create a clipboard.js file, along app.js. This contains all my functions and exports them for import and use anywhere else.

function copyTo() { /*...*/ }
function copyFrom() { /*...*/ }
export { copyTo, copyFrom }

So in app.js i can simply write

import {copyTo} from "./clipboard"
copyTo("Hello")

This works, because the code from both file runs through webpack.
But when trying to user this code in the <script> section of any component it’s a different story. The code there is just plain, unprocessed Javascript, so it’s not possible to use import ... and access all my clipboard function.

Current workaround for me is to import the functions in app.js and then assign them to the global window object. But in order to get this working i had to remove the defer attribute from the <script> tag in root.html.leex, in order to get the Code in app.js executed before the Javascript code in my component. This all seems very hacky to me for such a seemingly simple task.

So my question is, what is the correct (or at leat more elegant way) to share Javascript code between components and how to use Javascript code that is organized in modules that need to be imported outside app.js?

Isnt this a common scenario with all packages/libraries installed via npm?

Thanks in advance,
Jens

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#applying_the_module_to_your_html

Using "type="module" on your script tags allow you to use imports and such, but it doesn’t seem to work on IE (if that’s a concern) .

Other than that probably using web pack to generate other entry points that can then be used as a regular scripts?

I think modules in JS and JS in overall are complex and hacky, that’s why you need bundlers, transpilers, syntax fixers, pre and post processors and everything else since it has to produce code for different targets each one following it’s own whims, syntax that is not implemented directly by vendors while they work on it, etc. This works sort of okeish when staying completely in nodejs realm, but easily breaks down outside of it.

I also don’t see much issue with having a namespace in window from where I can access shared functions, but yeah, it’s not as clean as using import.

Another way is to tell webpack to build not only app.js, but also a library. These library functions will be accessible through an entry point.

It is possible to return an array, with first being the normal build, and second being the library build.

Great suggestions, didn’t know about this.
Browser support is also pretty okay:
https://caniuse.com/es6-module
I changed my webpack.config.js to include my clipboard module:

    entry: {
      'app': glob.sync('./vendor/**/*.js').concat(['./js/app.js']),
      'clipboard': './clipboard.js'
    },

And then could import the module from my component when using the type=module attribute:

  <script type="module">
    import { copyTo } from "/js/clipboard.js"
    copyTo("Hello")
  </script>

Gets the job done!
But i will also look into LiveView hooks, maybe the give me another angle.

Thanks very much,
Jens

1 Like

For the life of me I couldn’t get this setup working for some reason.

I keep getting the error message could not find a name export named default from my “clipboard.js” file.

Is there anything else that you had to add into your webpack configuration to implement this solution? I’ve, unfortunately, had to revert to attaching behavior onto the window for now.

My clipboard.js looks like this.

function copyTo(text) {
  console.log(text);
  // ...
}

export { copyTo  }

More infos on named/default exports: export - JavaScript | MDN

Did You try to import copyTo, or import { copyTo }?

It looks like You tried the first, from the error message You have…