Include javascript file in a template with Phoenix 1.4

I’ve got an phoenix 1.4.11 app and I want to include a javascript file in one of my template. The way I would do this with earlier versions of phoenix is described in this stackoverflow answer:https://stackoverflow.com/a/40203763/2272115. If I try to do it the way described in the answer, I get an error in the browser about Uncaught ReferenceError: require is not defined. How do I include a javascript file in a template using phoenix 1.4?

webpack.config.js

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

module.exports = (env, options) => ({
  optimization: {
    minimizer: [
      new TerserPlugin({ cache: true, parallel: true, sourceMap: false }),
      new OptimizeCSSAssetsPlugin({})
    ]
  },
  entry: {
    './js/app.js': glob.sync('./vendor/**/*.js').concat(['./js/app.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: [MiniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({ filename: '../css/app.css' }),
    new CopyWebpackPlugin([{ from: 'static/', to: '../' }])
  ]
});

assets/js/graph.js
export const Graph = { draw() { const Chart = require('chart.js'); const ctx = document.getElementById('myChart'); const myChart = new Chart(ctx, { type: 'bar', data: { labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], datasets: [{ label: '# of Votes', data: [12, 19, 3, 5, 2, 3], backgroundColor: [ 'rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)', 'rgba(255, 206, 86, 0.2)', 'rgba(75, 192, 192, 0.2)', 'rgba(153, 102, 255, 0.2)', 'rgba(255, 159, 64, 0.2)' ], borderColor: [ 'rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)' ], borderWidth: 1 }] }, options: { scales: { yAxes: [{ ticks: { beginAtZero: true } }] } } }); } }
index.html.eex

<div class="phx-hero">
  <h2>Graph: </h2>
  <canvas id="myChart" width="400" height="400"></canvas>
</div>
<%= render_existing @view_module, "scripts.html", assigns %>

GraphView render function

  def render("scripts.html", assigns) do
    ~s[<script>require('/js/graph').Graph.draw()</script>]
    |> raw
  end

I don’t see my js file in the DOM and I don’t know how to include it properly in the template. I appreciate any help, thanks!

Hello and welcome.

You cannot use require in the browser… it won’t work anymore,

You might try to put graph.js into assets/static/js, so it will be copied to public/static/js and use this directly.

<script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/graph.js") %>"></script>

Or You can add another entry point to webpack and use a dynamic output, to load and execute your graph. For example this kind of output.

filename: 'js/[name].js',

from this kind of other entry…

import {Graph} from "./put/the/path/to/graph";
Graph.draw();

This will create as many outputs than your entries number.

You will then load the other generated js file, but only in the graph page.

2 Likes

You can add another entry point to webpack and use a dynamic output, to load and execute your graph. For example this kind of output.

Can you describe more about this approach? I updated my webpack.config.js:

...
  entry: {
    './js/app.js': glob.sync('./vendor/**/*.js').concat(['./js/app.js']),
    graph: './js/graph.js'
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, '../priv/static/js')
  },
...

So if I create assets/js/visualization.js:

import {Graph} from './graph';
Graph.draw();

Do I need to make add another entry in the webpack config? Or is this what I was supposed to add to the webpack config?

Thanks for the reply @kokolegorille

I was thiking about something like this (not tested)

  entry: {
    app: glob.sync('./vendor/**/*.js').concat(['./js/app.js']),
    visualisation: './js/visualization.js'
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, '../priv/static/js')
  },

But You might not even need it, the graph.js should be accessible as /js/graph.js because it would be copied from assets/static/js/graph.js to priv/static/js/

probabbly I would change graph.js from

export ...

# to

const Graph=...

then, on the page, scripts should be able to access Graph object.

1 Like

Ok thank you @kokolegorille I was able to get it working:

  entry: {
    './app.js': glob.sync('./vendor/**/*.js').concat(['./js/app.js']),
    './graph.js': './js/graph.js'
  },
  output: {
    filename: '[name]',
    path: path.resolve(__dirname, '../priv/static/js')
  },

I’m trying to use chart.js and having similar problems. From this thread it looks like you’ve solved the problem. Can you detail out the specific steps you took to get this to work. I appreciate your response in advance.