skip to Main Content

After upgrading webpack 4.46.0 to webpack 5.88.2 & shakapacker 7.0.3 (rails 6.1.7.6) got the Uncaught ReferenceError: $ is not defined error. Here the current setup:

package.json

{
  "name": "app",
  "private": true,
  "dependencies": {
    "@babel/runtime": "^7.22.15",
    "@fortawesome/fontawesome-free": "^6.4.2",
    "@hotwired/turbo-rails": "^7.3.0",
    "@popperjs/core": "^2.11.8",
    "@rails/ujs": "^7.0.8",
    "babel-loader": "^9.1.3",
    "bootstrap": "^5.3.1",
    "core-js": "^3.32.2",
    "corejs-typeahead": "^1.3.3",
    "css-loader": "^6.8.1",
    "exports-loader": "^4.0.0",
    "expose-loader": "^4.1.0",
    "file-loader": "^6.2.0",
    "flag-icons": "^6.11.0",
    "handlebars": "^4.7.8",
    "inflection": "^2.0.1",
    "jbuilder": "^0.0.5",
    "jquery": "^3.7.1",
    "jquery-ui": "^1.13.2",
    "jquery-ui-dist": "^1.13.2",
    "jquery-ujs": "^1.2.3",
    "jstree": "^3.3.15",
    "mini-css-extract-plugin": "^2.7.6",
    "moment": "^2.29.4",
    "popper.js": "^1.16.1",
    "postcss": "^8.4.29",
    "rails-erb-loader": "^5.5.2",
    "sass-loader": "^13.3.2",
    "shakapacker": "^7.0.3",
    "sidekiq": "^1.1.1",
    "style-loader": "^3.3.3",
    "webpack": "^5.88.2",
    "webpack-assets-manifest": "^5.1.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1",
    "webpack-merge": "^5.9.0",
    "webpack-sources": "^3.2.3",
    "yarn": "^1.22.19"
  },
  "babel": {
    "presets": [
      "./node_modules/shakapacker/package/babel/preset.js"
    ]
  },
  "devDependencies": {
    "@babel/core": "^7.22.19",
    "@babel/plugin-proposal-class-properties": "^7.18.6",
    "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
    "@babel/plugin-proposal-private-methods": "^7.18.6",
    "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
    "@babel/plugin-transform-destructuring": "^7.22.15",
    "@babel/plugin-transform-regenerator": "^7.22.10",
    "@babel/plugin-transform-runtime": "^7.22.15",
    "@babel/preset-env": "^7.22.15",
    "babel-plugin-macros": "^3.1.0",
    "clean-webpack-plugin": "^4.0.0",
    "compression-webpack-plugin": "^10.0.0",
    "moment-timezone": "^0.5.43",
    "regenerator-runtime": "^0.14.0",
    "sass": "^1.67.0",
    "stylus": "^0.60.0",
    "terser-webpack-plugin": "^5.3.9",
    "url-loader": "^4.1.1"
  }
}

config/webpack/webpack.config.js

const { generateWebpackConfig } = require('shakapacker')
const webpackConfig = generateWebpackConfig()
const customConfig = require('./custom')
module.exports = generateWebpackConfig()

config/webpack/custom.js

 var path = require('path');
 var webpack = require('webpack');
 const MiniCssExtractPlugin = require("mini-css-extract-plugin");
 const devMode = (process.env.NODE_ENV !== "staging") && (process.env.NODE_ENV !== "production");

 module.exports = {
   mode: 'development',
   entry: '../../app/javascript/packs/application.js',
   output: {
     path: path.resolve(__dirname, '../public'),
     publicPath: 'public/',
     filename: '[name].js',   // could be omitted, that's the default
     assetModuleFilename: 'public/images/[name].[ext]'
   },
   resolve: {
     modules: ['node_modules'],
     extensions: ['.css', '.sass', '.scss', '.js', '.json'],
     alias: {
       jquery: 'jquery',
       'jquery-ui': 'jquery-ui/jquery-ui.js',
       typeahead: 'core-typeahead',
     }
   },
   module: {
     rules: [
       {
         test: /.html.erb$/,
         loader: 'rails-erb-loader'
       },
       {
         test: require.resolve('jquery'),
         loader: 'expose-loader',
         options: {
           exposes: ['$', 'jQuery'],
         },
       },
       {
         test: /.s[ac]ss$/i,
         use: [
           devMode ? "style-loader" : MiniCssExtractPlugin.loader,
           "css-loader",
           "postcss-loader",
           "sass-loader",
           "style-loader",
         ],
       },
       {
         test: /.(jpe?g|png|gif|svg|gif|png|jpg|eot|ttf|otf|woff|woff2)$/i,
         loader: 'file-loader'
       },
     ]
   },
   performance: {
     hints: false,
     maxEntrypointSize: 512000,
     maxAssetSize: 512000
   },
   optimization: {
     splitChunks: {
       chunks: 'all'
     }
   },
   progress: true,
   stats: {
     errorDetails: true, //this does show errors
     colors: true,
     modules: true,
     reasons: true
   }
 }

config/webpack/environment.js

const webpack = require('webpack');
const { generateWebpackConfig } = require('shakapacker')
const erb = require('./loaders/erb');

const customConfig = {
  resolve: {
    fallback: {
      dgram: false,
      fs: false,
      net: false,
      tls: false,
      child_process: false
    }
  }
};

environment.plugins.prepend('Provide', 
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    jquery: 'jquery',
    Popper: ['popper.js', 'default'],
    Rails: ['@rails/ujs'],
    moment: 'moment'
  })
);

environment.loaders.prepend('erb', erb);
environment.config.merge(customConfig);
module.exports = environment;

config/webpack/development.js

process.env.NODE_ENV = process.env.NODE_ENV || 'development'
const environment = require('./environment')
module.exports = environment.toWebpackConfig()

app/assets/javascript/packs/application.js

...
import $ from 'jquery';
window.jQuery = $;
window.$ = $;
import 'jquery-ui-dist/jquery-ui';
...

app/views/layouts/application.html.erb

...
<%= stylesheet_pack_tag 'application', :media => "all", :data => {:turbo => {:track => 'reload'}} %>
<%= javascript_pack_tag 'application', :data => {:turbo => {:track => 'reload'}, :defer => false} %>
...

No compilation errors occur, any clue why isn’t jquery loading?

2

Answers


  1. Chosen as BEST ANSWER

    I figured out that the error wasn't related to webpacker/shakapacker configuration, but to a js.erb file which was not loaded properly due to the missing 'defer' attribute.

    app/views/layouts/application.html.erb

    ...
    <%= stylesheet_pack_tag 'application', :media => "all", :data => {:turbo => {:track => 'reload'}} %>
    <%= javascript_pack_tag 'application', :data => {:turbo => {:track => 'reload'}} %>
    <%= javascript_include_tag '/js/browse_jstree.js' %>
    ...
    

    The Webpacker helper javascript_pack_tag will generate the HTML code with defer set to true as default (which is new in webpacker 5).

    ...
    <script src="/packs/js/runtime-f910d2d79960fb33a2bb.js" data-turbo="{&quot;track&quot;:&quot;reload&quot;}" defer="defer"></script>
    <script src="/packs/js/vendors-node_modules_hotwired_turbo_dist_turbo_es2017-esm_js-node_modules_rails_ujs_lib_asset-a849ed-49174ef89c67613e1ea1.js" data-turbo="{&quot;track&quot;:&quot;reload&quot;}" defer="defer"></script>
    <script src="/packs/js/application-3f6a4df6f831f107218d.js" data-turbo="{&quot;track&quot;:&quot;reload&quot;}" defer="defer"></script>
    <script src="/js/browse_jstree.js"></script>
    ...
    

    Therefore the js.erb file won't wait for the script and it will load before jquery. Adding defer = true to javascript_include_tag fixed the issue

    ...
    <%= javascript_include_tag '/js/browse_jstree.js', :defer => true %>
    ....
    

  2. Please check out Shakapacker README file and v6 upgrade docs in which two approaches are proposed. I quote them here:

    1. Using expose-loader

      If you expose jquery globally with expose-loader, by using import $ from "expose-loader?exposes=$,jQuery!jquery" in your app/javascript/application.js, pass the option defer: false to your javascript_pack_tag.

    2. Use resolve.alias in webpack config:

      // config/webpack/custom.js
      module.exports = {
        resolve: {
          alias: {
            jquery: 'jquery/src/jquery',
          }
        }
      }
      
    3. Use ProvidePlugin in webpack config

      
      module.exports = {
        // ...
        plugins: [
          new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery'
          })
        ]
        // ...
      };
      
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search