How do i reference javascript in app.js in a Phoenix template?

How do I refer to javascript defined app.js in a template. Say I want to add an onsubmit to a form… I’ve tried this at the bottom of a template:

<script type="text/javascript">
require("static/js/app.js")
</script>

But I get Uncaught ReferenceError: require is not defined However I know the js in app.js is there on the page because if I put this in app.js:

console.log("loaded")

I see that in the console. But if I do this in app.js:

export const log = (x) => alert(x)

Then put this in the bottom of my template:

<script type="text/javascript">
log("From template")
</script>

I get log is not defined

I thought using require in the browser was not allowed anymore.

And script in phoenix has defer. Function might not be available (until page has loaded).

I would use a library build for this use case…

Depending on the browser might be able to use import

require is a nodejs keyword.

1 Like

To complete my previous answer… having a lib build looks like this.

  1. Update webpack.config.js to return a list instead of the original return
module.exports = (env, options) => {
  return [
    ... usual stuff,
    // Library Bundle
    {
      entry: {
        my_libs: './js/my_libs.js',
      },
      output: {
        filename: 'js/[name].js',
        path: path.resolve(__dirname, '../priv/static'),
        libraryTarget: 'var',
        library: 'EntryPoint'
      },
      module: {
        rules: [
          {
            test: /\.js$/,
            exclude: /node_modules/,
            use: {
              loader: 'babel-loader'
            }
          },
        ]
      },
    }
  ]
}

Note the entry point of the library.

Now, You can define some functions in my_libs.js

const myFunc = () => { ... do stuff }
export { myFunc }

And use in templates

<script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/my_libs.js") %>"></script>
<script>
  window.onload = () => {
    EntryPoint.myFunc();
  }
</script>

I do recognize it is not so easy, but webpack is not so easy too :slight_smile:

This is code for webpack 4, I don’t know if it is still valid for webpack 5.

This still doesn’t work…

If I have it in app.js I don’t have to do the library thing.

app.js:

import "../css/app.scss"
import "phoenix_html"
import {Socket} from "phoenix"
import NProgress from "nprogress"
import {LiveSocket} from "phoenix_live_view"
import NavBarOpenListener from "./navbar.js"
import { AutoFocusHook } from "./hooks/autofocus.js"
import "@fortawesome/fontawesome-free/js/all"

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {hooks: {...AutoFocusHook},params: {_csrf_token: csrfToken}})

export const log = (x) => console.log(x)

window.addEventListener("phx:page-loading-start", info => NProgress.start())
window.addEventListener("phx:page-loading-stop", info => NProgress.done())
liveSocket.connect()
window.liveSocket = liveSocket

template:

<script type="text/javascript" >
  window.onload = () => {
    log("wot");
  }
</script>
 

Uncaught ReferenceError: log is not defined

and then, use the entry point as prefix. In your case that would be

EntryPoint.log()

maybe it can also work if You export as window.log, and use it as window.log

You cannot export log and use it unless You bind it to something, I guess it is not allowed anymore.

If you are doing this in multiple templates it might overwrite the window.onload callback and result in a weird race condition, have not actually tested though…

Assigning to window.log from app.js would work. Not the cleanest approach, relying on globals like that…

Yes exactly i don’t really like having globals at all. But i feel like this should be easy. Is that how everyone sprinkles js into their apps?

I gave up in the end and used live view. Astounded that I couldn’t get anything working.

If you were not using webpack, import/export would just work.

To do what you want with webpack requires some webpack foo.

If you don’t need asset bundling, could probably skip webpack. But good luck using any node dependencies…

Hope LiveView serves you well!

EDIT: webpack 5s support for module federation would help here I imagine…https://webpack.js.org/concepts/module-federation/

1 Like

Hi!

Here is what works for me, with tedious webpack:
In app.html.eex, last line in
<script src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
webpack.config.js:

const path = require('path');
const glob = require('glob');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');

module.exports = (env, options) => ({
  optimization: {
    minimizer: [
      new UglifyJsPlugin({ cache: true, parallel: true, sourceMap: false }),
      new OptimizeCSSAssetsPlugin({})
    ]
  },
  entry: {
      './js/app.js': ['./js/app.js'].concat(glob.sync('./vendor/**/*.js'))
  },
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, '../priv/static/js')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.(css)$/,
        use: ['style-loader', 'css-loader', 'postcss-loader']
      },
      {
        test: /\.(scss)$/,
          use: ['style-loader', 'css-loader', 'sass-loader']
      },
      {
        test: /\.(eot|svg|ttf|woff|woff2)$/,
        loader: "file-loader",
        options: {
           name: "[name].[ext]",
           outputPath: "../static/fonts/",
           publicPath: "../fonts/"
          }
      },
      {
        test: /\.(jpg|svg|png|gif)$/,
        loader: "file-loader",
        options: {
            name: "[name].[ext]",
            outputPath: "../static/images/",
            publicPath: "../images/"
        }
      }
    ]
  },
  plugins: [
      new MiniCssExtractPlugin({ filename: '../css/app.css' }),
      new CopyWebpackPlugin([{ from: 'static/', to: '../' }]),
      new webpack.ProvidePlugin({$: "jquery", jQuery: "jquery", "window.jQuery": "jquery"})
  ]
});

Regards, Karlo.