Imported vendor JS files in app.js are being loaded in the wrong order (Phoenix 1.4.0, Webpack)

Bootstrap.js requires jquery and popper.js to be loaded before it to work. So I specified:

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

// 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 'jquery';
import 'popper.js';
import 'bootstrap';

// Import local files
//
// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".

// import socket from "./socket"

import { startLesson } from './lesson/show.ts';

in my app.js

However, the generated app.js looks like this:

/***/ "../deps/phoenix_html/priv/static/phoenix_html.js":
/*!********************************************************!*\
  !*** ../deps/phoenix_html/priv/static/phoenix_html.js ***!
  \********************************************************/
...

/***/ "./css/app.scss":
/*!**********************!*\
  !*** ./css/app.scss ***!
  \**********************/
...

/***/ "./js/app.js":
/*!*******************!*\
  !*** ./js/app.js ***!
  \*******************/
...

/***/ "./js/lesson/show.ts":
/*!***************************!*\
  !*** ./js/lesson/show.ts ***!
  \***************************/
...

/***/ "./node_modules/bootstrap/dist/js/bootstrap.js":
/*!*****************************************************!*\
  !*** ./node_modules/bootstrap/dist/js/bootstrap.js ***!
  \*****************************************************/
...

/***/ "./node_modules/jquery/dist/jquery.js":
/*!********************************************!*\
  !*** ./node_modules/jquery/dist/jquery.js ***!
  \********************************************/
...

/***/ "./node_modules/popper.js/dist/esm/popper.js":
/*!***************************************************!*\
  !*** ./node_modules/popper.js/dist/esm/popper.js ***!
  \***************************************************/
...

/***/ "./node_modules/webpack/buildin/global.js":
/*!***********************************!*\
  !*** (webpack)/buildin/global.js ***!
  \***********************************/
...

which apparently put the vendor JS files in alphabetical order and thus made bootstrap.js unusable.

Also, I think the custom JS files, i.e. ./js/app.js and ./js/lesson/show.ts should be loaded after the dependencies are loaded, right?

How should I change the Webpack configuration so that the vendor JS files are loaded in the correct order?

The current webpack.config.js:

const path = require('path');
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',
  // entry: './js/app.ts',
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, '../priv/static/js')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        use: 'ts-loader'
      },
      {
        test: /\.scss$/,
        include: [path.resolve(__dirname, 'css')],
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({ filename: '../css/app.css' }),
    new CopyWebpackPlugin([{ from: 'static/', to: '../' }])
  ]
});

I dont know much about Bootstrap’s JS but I think, with normal ES modules, bootstrap should specify its own imports and there should be no need for you to explicitly import bootstrap’s dependencies. Webpack’s dependency ordering mechanism should automatically put everything in the right order.

Inspecting bootstrap’s package.json more closely, I see that these are listed as “peer dependencies”; so that means that they are basically optional.

Yeah that’s what I thought as well as the official documentation said it should suffice to write import('bootstrap');, and I was doing that in the beginning. However the Bootstrap related JS code is apparently not working as I encountered this bug: https://stackoverflow.com/questions/25757968/bootstrap-modal-is-not-a-function. There seems to be something wrong with the load order between jQuery and Bootstrap.

1 Like

did you find a solution?

No. My use case was to show a modal, so in the end I just manually put a hidden button on the page and clicked it by finding its id. I don’t have any other use case for bootstrap-js yet so I haven’t looked at it since. Quite frustrating.

To split the vendor dependencies from app, just create another entry:

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

However, Webpack can handle this automatically:

optimization: {
  splitChunks: {
    cacheGroups: {
      commons: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendor',
        chunks: 'all'
      }
    }
  }
}

Further reading: