vuex-typescript icon indicating copy to clipboard operation
vuex-typescript copied to clipboard

After minified the script. Script can't be run on IE11

Open nextsummer33 opened this issue 7 years ago • 22 comments

It showed an error "Vuex handler functions must not be anonymous." If I don't minify the script, it works fine. Other modern browser did work with minified script.

nextsummer33 avatar Jan 12 '18 14:01 nextsummer33

Word around solution for webpack UglifyJSPlugin

Giving the options into UglifyJSPlugin. The main reason caused the script could not run on IE11 was keep_fnames. :) I took a long time to test each options in order to make the script works. :( Hope it can save someone life.

new UglifyJSPlugin({ uglifyOptions: { ecma: 5, compress: { keep_fnames: true }, warnings: false, mangle: { keep_fnames: true } }, parallel: 4 })

nextsummer33 avatar Jan 12 '18 15:01 nextsummer33

This doesn't work for me even without minification :/

I think the problem is with IE11 not supporting the name property on functions which is being used to qualify the store key here.

AleksueiR avatar Jan 22 '18 18:01 AleksueiR

@AleksueiR It did work on IE11. Please checkout the boilerplate for vuex-typescript. :) Your project may contain a third party npm library that using es6 syntax. https://github.com/Ku4nCheang/vue-testdrive

nextsummer33 avatar Jan 23 '18 03:01 nextsummer33

There are a few things going on here so i'll try to explain it best I can. First off as @AleksueiR mentioned IE11 doesn't support Function.name with the following syntax:

var test = {
    foo: function() { }
};
console.log(test.foo.name); // Doesn't work in IE11

Interestingly IE11 does support Function.name with the following syntax:

var test = {
    foo: function foo() { }
};
console.log(test.foo.name); // Prints 'foo'

Great, it works in at least one case so we can exploit that. Now the next issue is that TS transpiles object shorthand methods like the following:

// TS standard
var test = {
    foo() { }
};

// ===> TS compiled to ES5
var test = {
    foo: function() { }
};

Which as we saw earlier won't work in IE11. But there is hope. Fortunately Babel takes the following and converts it into what we want:

// ===> TS compiled to ES5
var test = {
    foo: function() { }
};

// ===> Babel compiled ES5 to ES5
var test = {
    foo: function foo() { }
};

So you can by-pass this issue by setting up your build tool to process TS=>ES5 then have babel make another run over the ES5 generating the named functions that allow the Function.name to work in IE11. Here's an example for webpack:

        {
          test: /\.ts$/,
          // TS => ATL => ES5 => Babel => ES5
          // Currently this is due to vuex-typescript trying to rely on Function.name.
          // In our stores we use { f() { } } which generates: { f: function() { } }
          // Then vuex-typescript tries do `obj.f.name` which fails in IE11 if you don't have a named function.
          // Passing through babel will generate the following: { f: function f() { } }.
          use: [
            {
              loader: 'babel-loader',
              query: { plugins: ['transform-runtime'] }
            },
            'awesome-typescript-loader'
          ]
        },

Note: Babel is configured with preset-es2015 and preset-stage-2.

cha55son avatar Jan 27 '18 01:01 cha55son

@cha55son is right, a function needs to be explicitly named to have its name property set when it's used in an object literal, and you can do it with an additional Babel pass.

If you don't want or can't modify your build process, it's possible to set the _vuexKey on functions and it will be used instead of the name.

[getters, actions, mutations].forEach(dictionary =>
    Object.entries(dictionary).forEach(
        ([name, func]) => ((<any>func)._vuexKey = name)
    )
);

AleksueiR avatar Jan 27 '18 12:01 AleksueiR

Thanks @Ku4nCheang. Using UglifyJSPlugin works as expected and it saved my day.

so-sanghuynh avatar Apr 03 '18 09:04 so-sanghuynh

Hey there,

A little message to contribute to this issue. I am working on a SharePoint SPFX Webpart and my ts.config produces ES5 JS code.

When minified, as expected my code was not working on IE with the "Vuex handler functions must not be anonymous." error. I ran Babel in my gulp build and I could see my getters/mutations/actions look like this in the minified JS:

var test = {
    foo: function foo() { }
};

Unfortunaly, it was still not working under IE11 (although it should!).

I managed to make it work under all browsers using the class pattern you can find here: https://github.com/istrib/vuex-typescript/blob/master/src/tests/withModules/store/system/system.ts

Make sure to keep the exact same name between the class function and the one in your mutations/getters/actions otherwise you will meet this kind of error: [vuex] unknown mutation type

super2ni avatar May 03 '18 07:05 super2ni

@akhilvvs What is the line of code that raises the mentioned error?

super2ni avatar Jun 07 '18 14:06 super2ni

@super2ni below line where i dispatch the action

export const dispatchFetchSomeData = dispatch(actions.fetchSomeData) // this raises an error

vvimjam avatar Jun 07 '18 14:06 vvimjam

Is "personModuleHandlers" in the following code an instance of class personModuleHandlers? export const FunctionModule = { actions: { fetchSomeData: personModuleHandlers.fetchSomeData, }, }

I would try something like this: `class PersonModuleHandlers { @Handler
public async fetchSomeData (state: PersonState) : Promise { } }

export const personModuleHandlers = new PersonModuleHandlers (); export const FunctionModule = { actions: { fetchSomeData: personModuleHandlers.fetchSomeData,
}, } const actions = PersonModule.actions export const dispatchFetchSomeData = dispatch(personModuleHandlers .fetchSomeData) `

super2ni avatar Jun 07 '18 15:06 super2ni

@Ku4nCheang can you please paste your webpack config? Your suggestion did not work for me.

goors avatar Jun 11 '18 10:06 goors

@goors Are u sure it is the same issue? Maybe you can try @AleksueiR ‘s method. All u need to do is pasting the code on each store script file.

nextsummer33 avatar Jun 11 '18 11:06 nextsummer33

Just my 2 cents. Anyone still having issues after using @AleksueiR solution make sure to do the _vuexKey handling right after declaring the vuex store module/basket & before exposing the vuex-typescript functions.

For ES5 this will act as object.entries alternative, or you can wrap this in a function and call it.

if (!Object.entries)
  Object.entries = function( obj ){
    var ownProps = Object.keys( obj ),
        i = ownProps.length,
        resArray = new Array(i); // preallocate the Array
    while (i--)
      resArray[i] = [ownProps[i], obj[ownProps[i]]];
    
    return resArray;
  };

ref: Object.entries

vvimjam avatar Jun 12 '18 15:06 vvimjam

In Vue CLI you can solve this by adding the following to your vue.config.js:

  configureWebpack: (config) => {
    if (process.env.NODE_ENV === 'production') {
      config.optimization.minimizer[0].options.uglifyOptions = Object.assign(
        {},
        config.optimization.minimizer[0].options.uglifyOptions,
        {
          ecma: 5,
          compress: {
            keep_fnames: true,
          },
          warnings: false,
          mangle: {
            keep_fnames: true,
          },
        },
      );
    }
  },

Gaya avatar Aug 02 '18 09:08 Gaya

Awesome @Gaya , it works for Vue CLI 3.


I also needed to add chainWebpack, in my case for Vuetify.js.

So this is how my vue.config.js file ends up looking, in case it is useful as inspiration for someone else after me. :slightly_smiling_face:

module.exports = {
  // Fix Vuex-typescript in prod: https://github.com/istrib/vuex-typescript/issues/13#issuecomment-409869231
  configureWebpack: (config) => {
    if (process.env.NODE_ENV === 'production') {
      config.optimization.minimizer[0].options.uglifyOptions = Object.assign(
        {},
        config.optimization.minimizer[0].options.uglifyOptions,
        {
          ecma: 5,
          compress: {
            keep_fnames: true,
          },
          warnings: false,
          mangle: {
            keep_fnames: true,
          },
        },
      );
    }
  },
  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .loader('vue-loader')
      .tap(options => Object.assign(options, {
        transformAssetUrls: {
          'v-img': ['src', 'lazy-src'],
          'v-card': 'src',
          'v-card-media': 'src',
          'v-responsive': 'src',
        }
      }));
  },
}

tiangolo avatar Nov 19 '18 19:11 tiangolo

In more recent versions of Vue CLI (3.1.0+), they use https://github.com/terser-js/terser instead of uglify-es.

So, to make it work the configuration must be placed in terserOptions instead of uglifyOptions.

Here's my new vue.config.js:

module.exports = {
  // Fix Vuex-typescript in prod: https://github.com/istrib/vuex-typescript/issues/13#issuecomment-409869231
  configureWebpack: (config) => {
    if (process.env.NODE_ENV === 'production') {
      config.optimization.minimizer[0].options.terserOptions = Object.assign(
        {},
        config.optimization.minimizer[0].options.terserOptions,
        {
          ecma: 5,
          compress: {
            keep_fnames: true,
          },
          warnings: false,
          mangle: {
            keep_fnames: true,
          },
        },
      );
    }
  },

  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .loader('vue-loader')
      .tap(options => Object.assign(options, {
        transformAssetUrls: {
          'v-img': ['src', 'lazy-src'],
          'v-card': 'src',
          'v-card-media': 'src',
          'v-responsive': 'src',
        }
      }));
  },
}

tiangolo avatar Jan 31 '19 07:01 tiangolo

I really struggle with this problem, I'm currently on a project that uses vue-typex. I just can't get it to work. I'm not sure what to look for anymore. I've prepared a repro here. I now that vuex-typex is a different package, but maybe somebody here can help me out as well. I'll post the issue on vuex-typex as well, and crosslink it. Thanks

biohazard999 avatar Mar 21 '19 13:03 biohazard999

@biohazard999 Work around solution already mentioned. You can either change the minifier settings or add the workaround script in each store file.

nextsummer33 avatar Mar 21 '19 15:03 nextsummer33

@bbzy853 As in the repro I changed the minifier settings, but it does not work for me. I think the babel settings are wrong, but I have no idea how to do the TS->ES5->Babel->ES5 transformation to preserve the function names.

biohazard999 avatar Mar 21 '19 16:03 biohazard999

@bbzy853 the strange thing is, it doesn't even work without minification -.- Or I am completely wrong and it is a vuex-typex problem :/

biohazard999 avatar Mar 21 '19 16:03 biohazard999

@biohazard999 what kind of error message did you receive? You couldn’t get it works even without minification. Some situations may cause like below:

  1. sometimes you adopted an es6 function that supposed not work in IE11. Ployfill for specified function must be used in order to work.

  2. use require(“your vue file path”).default, missing “default” will cause Vue renderer does not work. latest Vue compiler need it in order to work.

  3. Wepack setting. Make sure it works on Chrome first.

nextsummer33 avatar Mar 22 '19 01:03 nextsummer33

@bbzy853 Thanks for your answer! For the sake of completeness, here is the error message.

SCRIPT5022: Vuex handler functions must not be anonymous. Possible causes: fat-arrow functions, uglify.  To fix, pass a unique name as a second parameter after your callback.

@rugia813 gave me the right hint in the vuex-typex repo. In vuex-typex I need to specify the name before exporting: For non IE this works.

 commitOpenState: b.commit(setOpenState),

  dispatchToggleNavigation: b.dispatch(toggleNavigation),
  dispatchCloseNavigation: b.dispatch(closeNavigation),

For IE this needs to be done:

  commitOpenState: b.commit(setOpenState, 'commitOpenState'),

  dispatchToggleNavigation: b.dispatch(toggleNavigation, 'toggleNavigation'),
  dispatchCloseNavigation: b.dispatch(closeNavigation, 'closeNavigation'),

In combination with the webpack terser options:

module.exports = {
  // Fix Vuex-typescript in prod: https://github.com/istrib/vuex-typescript/issues/13#issuecomment-409869231
  configureWebpack: (config) => {
    if (process.env.NODE_ENV === 'production') {
      config.optimization.minimizer[0].options.terserOptions = Object.assign(
        {},
        config.optimization.minimizer[0].options.terserOptions,
        {
          ecma: 5,
          compress: {
            keep_fnames: true,
          },
          warnings: false,
          mangle: {
            keep_fnames: true,
          },
        },
      );
    }
  },
}

It works fine. Thanks again for your help!

biohazard999 avatar Mar 22 '19 07:03 biohazard999