How to import/link Javascript files?

I’m trying to integrate the javascript from a bootstrap theme into phoenix 1.4 and failing to get the javascript files to work. I’ve placed jquery into assets/vendor, which webpack output is showing as working.

But I’ve found little documentation and confusing posts about how to use assets/js/app.js to import single javascript files from the same folder (assets/js) and how these can be required/used on a per page (template) level and whether this is possible or recommended. Some people reference individual functions and others entire javascript files. I’ve tried importing javascrtip files in assets/js/app.js, but must be doing it wrong as webpack isn’t seeing them, and I’m not sure if I should be using a script tag in the relevant template to call wrapped javascript function/file if/when webpack finds it in assets/js/app.js.

My last question is - with HTTP2, is using assets/js/app.js and packing a single file via webpack the best approach or should I be sending single javascript files down the wire, which appears to be easier anyway as the docs aren’t clear how to import javascript files in assets/js/app.js?

Hello, welcome.

Please show some code… especially app.js.

Maybe look at

or (Phoenix 1.3, but it should not be different).

Hi, welcome to the forum. The configuration is very basic and general to give you a starting point, but you may want to tweak it a bit in order to install new dependencies. This should work assuming you have installed the dependencies via NPM or Yarn:

entry: {
  './js/app.js': ['./js/app.js', 'jquery', 'bootstrap']
},

For a more elaborated configuration you should take a look to Webpack’s documentation.

It depends. If you’re using a lot of JavaScript, you may want to split vendor’s dependencies from the bundle of your app. The idea of splitting a bundle is mandatory for SPAs, but for MPAs sometimes is better having a big bundle minified and gzipped (if its size isn’t too big) or using a CDN for the dependencies, as it results in less requests needed from the client.

Thanks @kokolegorille and @adrianrl, below are my webpack.config.js, package.json, NPM packages installed globally and locally (to my app’s assets directory), and terminal output from running the app.

@adrianrl, you’ll note that in my webpack.config.js, I don’t have the same entry as you’ve noted, but I haven’t seen it in the docs I’ve followed. How could I edit the current:

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

to be the same as you’ve suggested and do I need to if bootstrap appears to be rendering fine?

The issue I’m having is trying to import assets/js/custom.js into assets/js/app.js - what to write to do that, and whether or not I have to individually reference custom.js in the relevant index.html.eex file or whether I just leave it to <script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script> to import assets/js/custom.js globally - if I could import it.

Unless I’ve made an obvious mistake above, how do I import custom.js into app.js from the same directory, assets/js?

Comments in assets/js/app.js show what I’ve tried (and tried single quotes too):


// We need to import the CSS so that webpack will load it.
// The MiniCssExtractPlugin is used to separate it out into
// its own CSS file.

import 'bootstrap';
import css from "../css/app.scss";

// THIS ISN’T WORKING. I USED THE socket.js example at bottom of file. AM I REFERENCING custom.js wrong?
import custom from “./custom”;

//NOR IS THIS WORKING
import custom from “./custom.js”;

// webpack automatically bundles all modules in your
// entry points. Those entry points can be configured
// in "webpack.config.js".
//
// Import dependencies
//
import "phoenix_html"

// Import local files
//
// Local files can be imported directly using relative paths, for example:
// import socket from "./socket"

———————————————

Showing globally installed NPM packages:

$ npm list -g --depth 0

/Users/Me/.asdf/installs/nodejs/11.9.0/.npm/lib
├── sass@1.17.3
└── sass-lint@1.12.1

NPM packages installed local to my apps assets directory:

$ npm ls --depth=0

/Users/Me/dev/projects/someapp_all/someapp_exp/someapp/assets
├── @babel/core@7.3.3
├── @babel/preset-env@7.3.1
├── babel-loader@8.0.5
├── bootstrap@4.3.1
├── copy-webpack-plugin@4.6.0
├── css-loader@0.28.11
├── font-awesome@4.7.0
├── jquery@3.3.1
├── mini-css-extract-plugin@0.4.5
├── node-sass@4.11.0
├── optimize-css-assets-webpack-plugin@4.0.3
├── phoenix@1.4.1 -> /Users/Me/dev/projects/someapp_all/someapp_exp/someapp/deps/phoenix
├── phoenix_html@2.13.1 -> /Users/Me/dev/projects/someapp_all/someapp_exp/someapp/deps/phoenix_html
├── popper.js@1.14.7
├── sass-loader@7.1.0
├── uglifyjs-webpack-plugin@1.3.0
├── webpack@4.4.0
└── webpack-cli@2.1.5

———————————————

Here’s my 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');

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: [MiniCssExtractPlugin.loader, 'css-loader']
        // test: /\.scss$/,
        test: /\.s?css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
      // use: [
      //   MiniCssExtractPlugin.loader,
      //   {
      //     loader: 'css-loader',
      //     options: {}
      //   },
      //   {
      //     loader: 'sass-loader',
      //     options: {}
      //   }
      // ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({ filename: '../css/app.css' }),
    new CopyWebpackPlugin([{ from: 'static/', to: '../' }])
  ]
});

———————————————

Here’s my package.json

{
  "repository": {},
  "license": "MIT",
  "scripts": {
    "deploy": "webpack --mode production",
    "watch": "webpack --mode development --watch"
  },
  "dependencies": {
    "phoenix": "file:../deps/phoenix",
    "phoenix_html": "file:../deps/phoenix_html"
  },
  "devDependencies": {
    "@babel/core": "^7.0.0",
    "@babel/preset-env": "^7.0.0",
    "babel-loader": "^8.0.0",
    "bootstrap": "^4.3.1",
    "copy-webpack-plugin": "^4.5.0",
    "css-loader": "^0.28.10",
    "font-awesome": "^4.7.0",
    "jquery": "^3.3.1",
    "mini-css-extract-plugin": "^0.4.0",
    "node-sass": "^4.11.0",
    "optimize-css-assets-webpack-plugin": "^4.0.0",
    "popper.js": "^1.14.7",
    "sass-loader": "^7.1.0",
    "uglifyjs-webpack-plugin": "^1.2.4",
    "webpack": "4.4.0",
    "webpack-cli": "^2.0.10"
  }
}

———————————————

Here’s terminal output:

$ mix phx.server

[info] Running SomeappWeb.Endpoint with cowboy 2.6.1 at http://localhost:4000

Webpack is watching the files…

Hash: 6d107d6e1c01ad8ba578
Version: webpack 4.4.0
Time: 6045ms
Built at: 4/1/2019 9:37:23 AM
                               Asset       Size       Chunks             Chunk Names
                      ../css/app.css    185 KiB  ./js/app.js  [emitted]  ./js/app.js
                              app.js   1.32 MiB  ./js/app.js  [emitted]  ./js/app.js
                      ../favicon.ico   1.23 KiB               [emitted]
                        ../.DS_Store      6 KiB               [emitted]
                 ../images/.DS_Store      6 KiB               [emitted]
               ../images/about/1.jpg   6.82 KiB               [emitted]
               ../images/about/3.jpg   7.17 KiB               [emitted]
               ../images/about/4.jpg   4.79 KiB               [emitted]
      ../images/logos/designmodo.jpg   1.66 KiB               [emitted]
 ../images/logos/creative-market.jpg   1.72 KiB               [emitted]
          ../images/logos/envato.jpg   1.99 KiB               [emitted]
     ../images/logos/themeforest.jpg   1.65 KiB               [emitted]
                       ../robots.txt  202 bytes               [emitted]
               ../images/about/2.jpg     15 KiB               [emitted]
../images/portfolio/06-thumbnail.jpg   13.3 KiB               [emitted]
../images/portfolio/01-thumbnail.jpg   16.4 KiB               [emitted]
../images/portfolio/02-thumbnail.jpg   16.1 KiB               [emitted]
../images/portfolio/03-thumbnail.jpg   16.2 KiB               [emitted]
../images/portfolio/04-thumbnail.jpg   16.2 KiB               [emitted]
../images/portfolio/05-thumbnail.jpg   23.7 KiB               [emitted]
     ../images/portfolio/02-full.jpg   31.2 KiB               [emitted]
     ../images/portfolio/06-full.jpg   36.4 KiB               [emitted]
     ../images/portfolio/04-full.jpg   40.3 KiB               [emitted]
     ../images/portfolio/01-full.jpg   51.6 KiB               [emitted]
     ../images/portfolio/03-full.jpg   51.7 KiB               [emitted]
     ../images/portfolio/05-full.jpg    102 KiB               [emitted]
             ../images/header-bg.jpg    233 KiB               [emitted]
                ../images/team/1.jpg    209 KiB               [emitted]
                ../images/team/2.jpg    211 KiB               [emitted]
                ../images/team/3.jpg    221 KiB               [emitted]
             ../images/map-image.png    356 KiB               [emitted]
   [0] multi ./js/app.js ./vendor/jquery-easing/jquery.easing.compatibility.js ./vendor/jquery-easing/jquery.easing.js ./vendor/jquery-easing/jquery.easing.min.js ./vendor/jquery/jquery.js ./vendor/jquery/jquery.min.js ./vendor/jquery/jquery.slim.js ./vendor/jquery/jquery.slim.min.js 112 bytes {./js/app.js} [built]
[../deps/phoenix_html/priv/static/phoenix_html.js] 2.17 KiB {./js/app.js} [built]
[./css/app.scss] 39 bytes {./js/app.js} [built]
[./js/agency.js] 1.51 KiB {./js/app.js} [built]
[./js/app.js] 545 bytes {./js/app.js} [built]
[./node_modules/webpack/buildin/amd-options.js] (webpack)/buildin/amd-options.js 82 bytes {./js/app.js} [built]
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 509 bytes {./js/app.js} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 519 bytes {./js/app.js} [built]
[./vendor/jquery-easing/jquery.easing.compatibility.js] 1.89 KiB {./js/app.js} [built]
[./vendor/jquery-easing/jquery.easing.js] 4.95 KiB {./js/app.js} [built]
[./vendor/jquery-easing/jquery.easing.min.js] 4.58 KiB {./js/app.js} [built]
[./vendor/jquery/jquery.js] 292 KiB {./js/app.js} [built]
[./vendor/jquery/jquery.min.js] 130 KiB {./js/app.js} [built]
[./vendor/jquery/jquery.slim.js] 236 KiB {./js/app.js} [built]
[./vendor/jquery/jquery.slim.min.js] 105 KiB {./js/app.js} [built]
    + 4 hidden modules
Child mini-css-extract-plugin node_modules/css-loader/index.js!node_modules/sass-loader/lib/loader.js!css/app.scss:
    [./node_modules/css-loader/index.js!./node_modules/sass-loader/lib/loader.js!./css/app.scss] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./css/app.scss 193 KiB {mini-css-extract-plugin} [built]
        + 1 hidden module
[info] GET /
[debug] Processing with SomeappWeb.PageController.index/2
  Parameters: %{}
  Pipelines: [:browser]
[info] Sent 200 in 45ms
[info] Replied phoenix:live_reload :ok
Hash: de47358b2665879465ae
Version: webpack 4.4.0
Time: 66ms
Built at: 4/1/2019 9:40:13 AM
 Asset      Size       Chunks             Chunk Names
app.js  1.32 MiB  ./js/app.js  [emitted]  ./js/app.js
 + 1 hidden asset
[./js/app.js] 548 bytes {./js/app.js} [built]
    + 18 hidden modules

———————————————

I hope that helps!

Sorry - custom.js isn’t showing about as I renamed it agency.js - and have got webpack finding it.

But my issue now appears to be… how to import/require it in index.html.eex as it’s not working in the render html and javascript?

If You want to import some js files, You need to export from them.

It would look like

export default …

If it is just some js without export, then You could put this into static, this will be pushed into priv/static, then it would be possible to link directly from html template.

What is inside your agency.js?

Also, webpack default config is simple, but complex config can be REALLY hard.

Concerning your second request, it is possible to optimize the output, for example, splitting app.js and vendor.js, split code per page (if You use a js router), lazy load part of js…

In fact, webpack is very good at that, but You need to read the doc to optimize its output.