speedskater/babel-plugin-rewire

Attempting to define property on object that is not extensible

Closed this issue · 34 comments

0x80 commented

I'm now trying to work with a Karma Webpack setup, and without even trying to mock anything I get the following error from my tests:

INFO [karma]: Karma v0.12.37 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.8 (Mac OS X 0.0.0)]: Connected on socket FV664eyGWLxApd_KTvrr with id 51361345
PhantomJS 1.9.8 (Mac OS X 0.0.0) ERROR
  TypeError: Attempting to define property on object that is not extensible.
  at /Users/me/myproject/app/components/__tests__/Something_test.js:26431

Any idea what might be wrong? I'm using babel 5.8 and webpack 1.10

I've set the webpack config to debug: 'inline-source-maps' but the line number from the error is not referencing the original souce. It's outside of the scope of this issue but if someone can give me advise on how to fix that I'd appreciate it :)

@0x80 I think the error is regression of making the export properties not enumerable. I would suggest to try to use babel-plugin-rewire version 0.1.12 in the mean time. If this does not fix your error could please provide a usage sample as a PR ?

0x80 commented

@speedskater with that version I'm getting a different error in my tests: TypeError: 'undefined' is not a function (evaluating '__$Setters__[name](value)')

And if I use the app code in a browser with the plugin on the loader I see Uncaught TypeError: __$Getters__[name] is not a function

I'm going to be on a holliday in about two hours and I'm afraid I won't be able to provide a good sample as a PR before then...

Okay. Thank you I will try to fix the problem asap. But it will take at least till tomorrow. So have a nice holliday and hopefully the bug is fixed afterwards :)

0x80 commented

👍

I ran into this problem when building rewire-global because of modules that were returning things like booleans which you can obviously not add properties to.

The fix that worked for me was to selectively expose the rewire functionality on modules that made sense to rewire at all: https://github.com/TheSavior/rewire-global/blob/master/index.js#L11

@TheSavior thanks for your suggestion. I think the problem should be solved by converting the primitive types to real objects.
@0x80 I thinks you can circumvent the problem by changing the exported primitive to a real object. e.g.

see: https://javascriptweblog.wordpress.com/2010/09/27/the-secret-life-of-javascript-primitives/

Will tackle the problem today evening.

It feels super sketchy to convert people's primitives to objects. And as the article mentions, that won't work for booleans.

With the basic rewire library, not every module has the get and set added, only the files that are explicitly called with rewire('module/path') have it added at runtime. This means that the user has control over what gets added.

Using rewireify, rewire-global, or babel-plugin-rewire means that every module at compile time is converted and then the user only uses specific files. That means that modules the user has no intention of ever looking into still need to work just as they would without rewire.

IMHO, It is better to only add the rewire functions to modules the user might potentially want to rewire instead of modifying potential logic and behavior (converting the primitives to objects) in every module.

I am just preparing. It is based on http://www.2ality.com/2011/04/javascript-converting-any-value-to.html. Thanks for your hint regarding boolean values. For boolean values I will use the normal assignment with standard properties. It has the drawback that enumerable properties are exported in this case but at least it should work.

Although I agree with you that changing only the code which is actually rewired would be nicer i think it is currently not possible. I think to achieve this a major rewrite would be required.

I guess I am making the claim that nobody ever wants to rewire modules that export primitives. So only adding the rewire methods on things that export functions and objects is safer than modifying possible behavior of people's code by converting the primitives up to objects.

@TheSavior I have just run into another issue and i think you are definitely right. Will publish the fix in some hours :).

@TheSavior, @0x80 The Problem should be fixed in the latest version 0.1.14. Only default exports which are of type function or object will get the rewire properties attached. Could you please let me know if the fix works for you.

kibin commented

That’s still issue for me. I’m trying to use it with karma and before I’m able to do anything I get

 PhantomJS 2.0.0 (Mac OS X 0.0.0)
          10) TypeError: undefined is not a constructor (evaluating '__$Getters__[name]()') in http://localhost:9876/base/tests.webpack.js?2d9ea7e187cd4e771e0f48bb55354ba0e32164be (line 57399)

My tests.webpack.js is dumb and simple:

var testsContent = require.context('./frontend/tests', true);

testsContent.keys().forEach(testsContent);

Version of plugin: babel-plugin-rewire@0.1.22 node_modules/babel-plugin-rewire

Am I missing something?

UPD: I tried to switch to Chrome and get new error:

Chrome 45.0.2454 (Mac OS X 10.10.5)
          1) TypeError: __$Setters__[name] is not a function
kibin commented

I placed a debugger in function __Rewire__ and discovered that name in arguments is undefined.

fobdy commented

I think this issue has to be reopened. We have the similar problem with webpack, babel, redux and babel-plugin-rewire@0.1.22 :(

Uncaught TypeError: __$Getters__[name] is not a function

@fobdy Thanks for reopening the issue. Could you please test it with 0.1.23-beta-3 ?

I ran into the same problem today - webpack, babel, redux, and babel-plugin-rewire@0.1.23-beta3. Unfortunately the issue isn't solved for us in this beta-3 release, we get the same error:

__$Setters__[name] is not a function

Please let me know if there's anything I can do to assist in debugging!

@jorispz thanks for testing. It would be great if you could create a PR with a sample, which reconstructs your problem. I will then try to fix it as soon as possible.

Here is example of issue with redux, babel, webpack demonstration: https://github.com/CrazyUmka/babel-plugin-rewire-issue28

Please reopen the issue (now it is in closed status)

Thanks for providing a sample. Is it possible for you to reduce the test case down to the smallest part that reproduces the error? And it doesn't look like your repo has a test suite that we can run to reproduce the error.

If you find that setting up that repo for this is too difficult, you might want to consider adding a fixture to this repo and adding a failing test here for us to fix.

I created a minimal testcase here: https://github.com/jorispz/babel-plugin-rewire-issue-28

As explained in the README, the problem can be reproduced by running 'npm run test', and disappears when not using the rewire plugin. The cause seems to be the use of Object.assign - see README for details.

I hope this helps, let me know if I can help further!

+1
We're also unable to use the plugin due to a similar error:
PhantomJS 1.9.7 (Mac OS X 0.0.0) ERROR TypeError: Attempting to change value of a readonly property. at /Users/you/Projects/myProject/test/unit/prototypes/jQueryAggregate.test.js:901

Sorry for the Delay on the latest issues but i am currently in h

on holidays and will return on the 23rd of november. I will then fix this bug and create a new beta.

@jorispz nice test case! I just stepped through that in Firefox and noticed something pretty interesting.

Here's some code within Redux:

    function assertReducerSanity(reducers) {
      Object.keys(reducers).forEach(function (key) {
        var reducer = reducers[key];
        var initialState = reducer(undefined, { type: _createStore.ActionTypes.INIT });

        if (typeof initialState === 'undefined') {
          throw new Error('Reducer "' + key + '" returned undefined during initialization. ' + 'If the state passed to the reducer is undefined, you must ' + 'explicitly return the initial state. The initial state may ' + 'not be undefined.');
        }

        var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.');
        if (typeof reducer(undefined, { type: type }) === 'undefined') {
          throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + _createStore.ActionTypes.INIT + ' or other actions in "redux/*" ') + 'namespace. They are considered private. Instead, you must return the ' + 'current state for any unknown actions, unless it is undefined, ' + 'in which case you must return the initial state, regardless of the ' + 'action type. The initial state may not be undefined.');
        }
      });
    }

The __GetDependency__ key, for instance, is being enumerated by Object.keys! I would have though all the rewire stuff would be added as non-enumerable...?

Here's a snippet from babel-plugin-rewire itself:

                    t.blockStatement([
                        addNonEnumerableProperty(t, defaultExportVariableId, '__Rewire__', universalAccessors['__Rewire__']),
                        addNonEnumerableProperty(t, defaultExportVariableId, '__set__', universalAccessors['__Rewire__']),
                        addNonEnumerableProperty(t, defaultExportVariableId, '__ResetDependency__', universalAccessors['__ResetDependency__']),
                        addNonEnumerableProperty(t, defaultExportVariableId, '__GetDependency__', universalAccessors['__GetDependency__']),
                        addNonEnumerableProperty(t, defaultExportVariableId, '__get__', universalAccessors['__GetDependency__']),
                        addNonEnumerableProperty(t, defaultExportVariableId, '__RewireAPI__', universalAccessors['__RewireAPI__'])
                    ])

So I'm wondering if the Object.assign polyfill is what's messing up!

Nevermind about that. But I'm thinking potentially this has something to do with it:

    exports.hideNotification = _hideNotificationOrig;
    exports.__GetDependency__ = _GetDependency__;
    exports.__get__ = _GetDependency__;
    exports.__Rewire__ = _Rewire__;
    exports.__set__ = _Rewire__;
    exports.__ResetDependency__ = _ResetDependency__;
    exports.__RewireAPI__ = _RewireAPI__;
    exports['default'] = _RewireAPI__;

so problem from OP was maybe... import foo from './foo' rather than import { foo } from './foo' or something?

As far as i have seen the problem in your PR is
import * as helloWorldReducers from './reducers.js';
The Problem is that i haven't found a solution how to prevent the wild card import to recognize the exports from rewire.
@sebmck do you have an idea how this could be achieved?

@0x80 @kibin @jorispz @CrazyUmka @TheSavior @Dygerati @adamdicarlo Please all have a look at the latest beta 1.0.0-beta-4 . Could you please let me know if this solves your issues.
Regarding the case of wildcard imports used in combination with reducers as mentioned in the previous comment it is still not fixed. And imho it is not possible to fix this issue. Therefore does any of you object if we close this issue.

@speedskater sorry to hear that it can't be fixed, but I'm fine with closing this issue. Thanks for the help!

I can confirm that in 1.0.0-beta-4 problem seems disappeared.

@jorispz maybe the wildcard export can be tackled, see #78. But it could take another three weeks and could break the rewire API.

@speedskater don't worry about it - we refactored things around a bit to work around this issue, so don't fix it on my behalf (though feel free to do so regardless :-)

By now we fixed wildcard imports as well. so we will close this bug. Keep in mind that having multiple wildcard exports in the same file is currently not supported by the plugin.

@speedskater

By now we fixed wildcard imports as well. so we will close this bug. Keep in mind that having multiple wildcard exports in the same file is currently not supported by the plugin.

How is that not in big bold letters in the README?