0

I am working on an ASP.NET project that uses Vue.js and jQuery. I have a JavaScript file (fields.js) that defines several classes (e.g., Users, Fields) and initializes a Vue instance. When I use the unminified version of the file, everything works as expected. However, when I use the minified version, I encounter runtime errors, such as:

TypeError: users.hasAllUsersSelected is not a function

I've confirmed that hasAllUsersSelected exists in the Users class and that the minified file is sent and received correctly (I can see the minified code in the browser's response tab).

This is a stripped down version of my set up:

fields.js

class Users {
  constructor(users = [], ebus) {
    this.users = users;
    this.filteredUsers = users;
    this.isDescSort = true;
    this.$ebus = ebus;
  }

  hasAllUsersSelected() {
    if (this.filteredUsers.length === 0) {
      return false;
    }
    return this.filteredUsers.every(user => user.isSelected);
  }
}

new Vue({
  data() {
    return {
      users: new Users([], new Vue()),
    };
  },
methods: {
  getUserList () { 
  let vals = [...document.querySelectorAll('#listEnrolledUsers > option')];

  let userList = vals.map(user => new User(user.textContent.trim(), user.value, false));
  userList = userList.filter((user, index) => index !== 0);

  this.users = new Users(userList, this.$ebus);
},
  mounted() {
    this.getUserList();
    console.log('Vue instance mounted');
    console.log('users.hasAllUsersSelected:', typeof this.users.hasAllUsersSelected);
  },
}).$mount('#app'); 

This file then gets minified by webpack (version ^5.99.9)

webpack.config.js

const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');

const outputFilename = '[name].[contenthash].js'

module.exports = (env = {}) => {

  const bundleDefinitions = [
    {
      name: 'fields',
      entry: { app:path.resolve(__dirname, '../path/to/fields/main/fields.js')},
      outputPath: path.resolve(__dirname, '../path/to/fields/bundle'),
      outputFilename: outputFilename,
      needSourceMap: true,
    },
  ];

  const wanted = env.bundles
    ? env.bundles.split(',').map(s => s.trim())
    : bundleDefinitions.map(b => b.name);

  const configs = bundleDefinitions
    .filter(b => wanted.includes(b.name))
    .map(b => ({
      name: b.name,
      mode: 'production',
      entry: b.entry,
      output: {
        path: b.outputPath,
        filename: outputFilename,
        chunkFilename: outputFilename,
        clean: true,
      },
      ...(b.needSourceMap ? { devtool: 'source-map' } : {}),
      cache: {
        type: 'filesystem',
        buildDependencies: { config: [__filename] },
      },
      optimization: {
        runtimeChunk: { name: `manifest.${b.name}` },
        splitChunks: {
            minSize: 0, 
          cacheGroups: {
            vendor: {
              test: /[\\/]node_modules[\\/]/,
              name: 'vendor',
              filename: b.vendorChunkName, 
              chunks: 'all',
               enforce: true,
            },
          },
        },
        minimize: true,
        minimizer: [
          new TerserPlugin({
            terserOptions: {
              compress: true,
              mangle: false,
              output: { comments: false },
            },
            extractComments: false,
          }),
        ],
      },

    }));

  return configs;
};

if I call fields.js directly in my .aspx page where I need that class ala:

<script type="text/javascript" src="/path/to/fields/main/fields.js"> </script> 

<span class="c9-clickable" v-on:click="users.toggleAllSelection()"><i v-bind:class="[users.hasAllUsersSelected() ? 'far fa-check-square' : 'far fa-square']"></i>Users</span>

Everything is fine.

But when I try to link the, successfully minified and recieved version of fields.js I get the above error.

<script type="text/javascript" src="/path/to/fields/bundle/<%= ManifestFileName %>"> </script> 
<script type="text/javascript"src="/path/to/fields/bundle/<%= AppFileName %>"> </script>

<span class="c9-clickable" v-on:click="users.toggleAllSelection()"><i v-bind:class="[users.hasAllUsersSelected() ? 'far fa-check-square' : 'far fa-square']"></i>Users</span>

(Don't worry about how I get the manifest/app filename, it works perfectly. Again, I see the correct file and content response in the dev tools)

For the record I do have some jquery in my aspx file that I did not include because it doesn't directly manipulate users.hasAllUsersSelected and, again, directly linking the unminified file works so I know it's not jquery and vue interferring with each other.

The minification works. The minified files are generated and received by the aspx file. I don't understand what's going on.

Can some sweet, kind soul help me figure out what i'm doing wrong here??

1
  • What vue.js version you're using? I'm using v3.5 and cannot reproduce your problem locally with my hand wavy setup. A minimal reproducible example is perhaps needed in this case. Or even attaching the problematic minified file can be helpful. Commented Jul 18 at 22:37

3 Answers 3

0

You should define a variable to receive the instance object of 'new Vue'.
like this below:
const vueInstance = new Vue({...})

Then you can use it like this below:

<span class="c9-clickable" v-on:click="vueInstance.users.toggleAllSelection()"><i v-bind:class="[users.hasAllUsersSelected() ? 'far fa-check-square' : 'far fa-square']"></i>Users</span>
Sign up to request clarification or add additional context in comments.

1 Comment

I *really appreciate the reply but this did not change the error or behavior in anyway
0

In your Vue app, you are creating a special object (new Users(...)) that has some functions inside it (like hasAllUsersSelected()).

In development mode (unminified), everything works fine.

But in production (minified), when you do this:

this.users = new Users(...);

you are replacing the whole users object. Vue's internal system doesn't fully understand it's a special object (a "class instance"), so it breaks the connection to its functions. That’s why hasAllUsersSelected() is missing.

Instead of replacing the whole users object, just update the values inside it:

data() {
  return {
    users: new Users([], new Vue()), 
  };
},
methods: {
  getUserList() {
    const vals = [...document.querySelectorAll('#listEnrolledUsers > option')];
    const userList = vals
      .map(user => new User(user.textContent.trim(), user.value, false))
      .filter((user, index) => index !== 0);

   
    this.users.users = userList;
    this.users.filteredUsers = userList;
  }
}

Comments

0

Analysis of the Minification Issue in Your Vue.js/Webpack Setup

Based on your description, the issue appears to be related to how class methods are being handled during minification, even though you've set mangle: false in your Terser configuration.

Key Observations

  1. The unminified version works perfectly

  2. The minified version fails with users.hasAllUsersSelected is not a function

  3. You've confirmed the minified file contains the method definition

  4. You're using Webpack 5 with TerserPlugin

Likely Causes and Solutions

1. Class Method Name Mangling (Despite Your Configuration)

Even though you have mangle: false, there might be some unexpected behavior. Try these Terser options:

javascript

new TerserPlugin({
  terserOptions: {
    compress: true,
    mangle: {
      properties: false, // Ensure property names aren't mangled
      keep_classnames: true, // Explicitly keep class names
      keep_fnames: true // Explicitly keep function names
    },
    output: { 
      comments: false,
      preserve_annotations: true // Helps with Vue reactivity
    },
  },
  extractComments: false,
}),

2. Vue Reactivity System Issue

When minifying, Vue's reactivity system might lose track of methods. Try:

javascript

data() {
  return {
    users: null // Initialize as null
  };
},
created() {
  this.users = new Users([], this.$ebus);
  // Explicitly define methods for Vue's reactivity
  this.users.hasAllUsersSelected = this.users.hasAllUsersSelected.bind(this.users);
}

3. Webpack Module Wrapping

Webpack might be wrapping your class in a way that breaks the prototype chain. Try:

javascript

// In your Users class definition
Object.defineProperty(Users.prototype, 'hasAllUsersSelected', {
  value: function() {
    if (this.filteredUsers.length === 0) {
      return false;
    }
    return this.filteredUsers.every(user => user.isSelected);
  },
  writable: true,
  configurable: true,
  enumerable: true // Important for Vue reactivity
});

4. Source Map Verification

Since you have source maps enabled, verify they're correctly referenced in the minified file and that the browser can load them. A mismatch here could cause debugging to show incorrect information.

5. Webpack Output Comparison

Compare the structure of:

  1. The working unminified version

  2. The non-working minified version

Look for differences in how the Users class is exported/imported.

Alternative Approach

If the above doesn't work, consider:

  1. Using @babel/plugin-transform-classes to ensure consistent class transformation

  2. Switching to Vue 3's Composition API which might handle minification better

  3. Explicitly exporting the Users class and importing it in your Vue component

javascript

// fields.js
export class Users { /* ... */ }

// In your Vue component
import { Users } from './fields.js';

This might provide better minification behavior.

Final Recommendation

Start with the Terser configuration changes (solution #1) as that's the most likely culprit given your symptoms. The fact that the method exists in the code but isn't recognized as a function suggests a name mangling or prototype chain issue during minification.

1 Comment

AI generated answers are not allowed on StackOverflow stackoverflow.com/help/gen-ai-policy

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.