jhnns / rewire-webpack

Dependency injection for webpack bundles
The Unlicense
121 stars 20 forks source link

rewire-webpack doesn't work with babel-loader #12

Open ryardley opened 9 years ago

ryardley commented 9 years ago

CommonJS syntax works

Currently I have the following karma config:

var webpack = require('webpack');
var RewirePlugin = require('rewire-webpack');
var rewirePlugin = new RewirePlugin();

module.exports = function (config) {

  config.set({
    files: ['mjs/my_test_rewire.js'],

    browsers: ['PhantomJS'],
    frameworks: ['jasmine'],

    preprocessors: {
      'mjs/my_test_rewire.js': ['webpack', 'sourcemap']
    },

    webpack: {
      resolve: {
        extensions: ['', '.js', '.jsx']
      },
      module: {
        loaders: [
          { test: /\.(js|jsx)$/, exclude: /node_modules/,
            loader: require.resolve('babel-loader') + '?optional=runtime' },
        ]
      },
      devtool: 'inline-source-map',
      plugins: [ rewirePlugin ]
    }
  });
};

Running the following karma test:

// my_test_rewire.js
var foo = require('./foo');
var rewire = require('rewire');

var fooRewired = rewire('./foo');
fooRewired.__set__('barText', 'dong!');

describe('Rewire test', function() {
  it('should say bar!', function() {
    expect(fooRewired.doBar()).toEqual('dong!');
  });
});

The test pulls in the following dependency:

// foo.js

var barText = require('./bar');

var foo = {
  doBar:function(){
    return barText;
  }
}

module.exports = foo;

Which pulls in another dependency to be mocked:

// bar.js
module.exports = 'bar!';

Which all works like a charm.

ES6 syntax does not

The problem is our source code is all transpiled using ES6 and ES6 modules. So the following does not work and causes an error:

//my_test_rewire.js

import 'es5-shim';
import 'jasmine-jquery/lib/jasmine-jquery';

import foo from './foo';
import rewire from 'rewire';

var fooRewired = rewire('./foo');
fooRewired.__set__('barText', 'dong!');

describe('Rewire test', function() {
  it('should say bar!', function() {
    expect(fooRewired.doBar()).toEqual('dong!');
  });
});
// foo.js
import barText from './bar';

var foo = {
  doBar:function(){
    return barText;
  }
}

export default foo;
// bar.js
export default 'bar!';

The error returned is:

PhantomJS 1.9.8 (Mac OS X) ERROR
  TypeError: 'undefined' is not an object (evaluating '__webpack_require__.m[module].call')
  at /Users/rudi/teg/teg-site/mjs/my_test_rewire.js:125:0 <- webpack:///~/rewire-webpack/lib/rewire.web.js:10:0
PhantomJS 1.9.8 (Mac OS X): Executed 0 of 0 ERROR (0.142 secs / 0 secs)

Even if the test remains in commonJS syntax the test runs but the rewiring fails. Are there any work arounds here at all until the point at which rewire will work with ES6?

doctyper commented 9 years ago

Just wanted to chime in here, experiencing the same issue. ES6 was working just fine with babel@<5.0. However upgrading to babel@^5.0 broke this functionality.

virajsanghvi commented 9 years ago

Also experiencing the same issue, with the same setup. Does anyone have any workarounds outside of downgrading babel? Seems like not using import statements works, and other ES6 syntax is fine.

jhnns commented 9 years ago

Mhmm ... I don't know much about babel, but rewire is using the given module loader to inject these setters, so as long as they don't mess with the module loader there should be no problem. Could you find out which variable is undefined?

doctyper commented 9 years ago

From my debugging, the problem comes from this line: https://github.com/jhnns/rewire-webpack/blob/master/lib/rewire.web.js#L10

In older versions of Babel, the value of module is the corresponding Webpack module id (i.e., if you import jquery, and Webpack maps that to an id of 5, then module = 5, and __webpack_modules__[module] becomes __webpack_modules__[5].

In Babel 5, the value of module is jquery, and __webpack_modules__[module] becomes __webpack_modules__["jquery"], which is undefined:

'undefined' is not an object (evaluating '__webpack_require__.m[module].call')

Best guess is that somewhere import statements are no longer being converted to Webpack ids. Not sure if this is a Babel thing, a Webpack thing, or a Rewire thing. But I'm not seeing this issue with any other plugin.

jhnns commented 9 years ago

So, does it work if you deactivate the babel compiler for all modules within rewire-webpack? They're plain old ES5 anyway :)

doctyper commented 9 years ago

Babel doesn't touch node_modules or rewire-webpack. Not in my environment anyway.

damassi commented 9 years ago

See https://github.com/jhnns/rewire/issues/55 as well.

sairion commented 9 years ago

Welp, what I'm experiencing is exactly same as what people saying. (I'm using karma+mocha+webpack+rewire+sinon) I just switched most of my codebase to ES6 module syntax and a few tests using webpack-rewire are failin' ... Is there anyone have duktape'd this problem?

sairion commented 9 years ago

I found where the problems comes from. Babel and rewire-webpack both actually causing the issue... There is two problems (caused by Babel > 5 's new module name transform rule). First one is importing rewire with module syntax, and second one is __set__ing (or other equiv api) es6-fied module.

First one:

import rewire from 'rewire'
    describe('test-1', function () {
        var fooModule = rewire('../foo-module');

gets transformed into

var _rewire = __webpack_require__(456);
var _rewire2 = _interopRequireWildcard(_rewire);

    describe('test-1', function () {
        var fooModule = _rewire2['default']('../foo-module');

You see the problem. ../foo-module didn't get transformed into proper Webpack module index (number) because Babel transformed rewire call with _interopRequireWildcard()'d rewire.

Second one, Rewire-webpack assumes modules to be declared within module scope as same varname (if import foomodule from 'foomodule' then rewire-webpack expects foomodule to be available in module)

When rewiring, rewire-webpack prints module and rewiring declaration (__set__, __get__)into another module. However, (I assume this behavior comes from Babel > 5) transforms module names get prefixed. like _foomodule, _foomodule2, etc. So foomodule won't be available in module level in this case.

Well, this is pretty much what I've found out so far, but not sure who's responsible to fix this. ;) I will also present this issue to Babel GH issues to see what is their response.

damassi commented 9 years ago

Thanks for tracking that down @sairion! Yeah, I just started noticing how Babel 5 is rewriting var form for quite a few things.

srph commented 9 years ago

Awesome, @sairion :+1:. I'm looking forward to any progress. Will see if I can help out too :smile:.

Just a question, would plain CommonJS require be a good work around for the issue? I'm using Babel as well, but I still use the CommonJS syntax for some libraries (compatibility reasons) in very few cases.

jhnns commented 9 years ago

Wow, that's tricky. Thx for investigating @sairion, that's really a big help!

jhnns commented 9 years ago

rewire and rewire-webpack are both affected. We could probably do something about the first problem (e.g. parsing the module when the rewire-loader is applied), but the second one is imho impossible to solve on my side.

wbinnssmith commented 9 years ago

If it helps, I've also set up a small repository demoing the conflict. Just run webpack and load the resulting bundle in a script tag to see the issue.

bradbarrow commented 9 years ago

:+1: What the latest on this? Encountering the same thing with webpack/karma/webpack-rewire

damassi commented 9 years ago

@bradbarrow, this is the suggested alternative: https://github.com/speedskater/babel-plugin-rewire

0x80 commented 9 years ago

The suggested alternative is not working for me. See https://github.com/speedskater/babel-plugin-rewire/issues/28. Did any of you encounter this before?

ufocoder commented 8 years ago

+1

indianscout commented 8 years ago

Any news on this so far?

I'm having the same issue with babel-plugin-rewire. A possible workaround is assigning the imported module to a variable:

import React from 'react';
import { defineMessages, injectIntl } from 'react-intl';

import LinkListPopover from 'elements/LinkListPopover';
...js

const LinkListPopoveR = LinkListPopover;

class NotificationPopover extends Component {
    static propTypes = propTypes;

    render() {
        const { formatMessage } = this.props.intl;

        const unread = filterTruthyValues(this.props.items, 'unread');

        const emptyState = {
            icon: 'check',
            text: formatMessage({ id: 'notificationPopoverEmpty' })
        };

        return (
            <LinkListPopoveR
...

Attention: but this only works when index.js imports the module in curly brackets like:

import { NotificationPopover } from './NotificationPopover';
export default NotificationPopover;

Removing {} causes the rewire to fail.

Here my deps:

    "babel-loader": "6.2.1",
    "babel-plugin-add-module-exports": "0.1.2",
    "babel-plugin-rewire": "1.0.0-beta-2",
    "babel-plugin-react-intl": "2.1.1",
    "babel-plugin-transform-class-properties": "6.4.0",
    "babel-plugin-transform-object-rest-spread": "6.3.13",
    "babel-polyfill": "6.3.14",
    "babel-preset-es2015": "6.3.13",
    "babel-preset-react": "6.3.13",
    "babel-register": "6.4.3",
    "webpack": "1.12.12",
    "webpack-dev-server": "1.14.1"
...
jhnns commented 8 years ago

I'm sorry, I don't know. ES2015 module have some new challenges, like immutable live-bindings, and since rewire is tightly coupled to the module system, it's not easy to find a good solution for this. The situation gets even more complicated since there is no JS engine yet that implements native ES2015 modules. So, a possible solution would be a workaround for the babel transpiler only. But I would like to find a solution, that will also work with native ES2015 modules.

munnerz commented 8 years ago

I'm experiencing the same issue using TypeScript as a loader in webpack:

TypeError: undefined is not an object (evaluating '__webpack_require__.m[module].call')

It does not work with either ES6 or require syntax (using import xyz = require('./xyz')).

Any progress? I'm unsure how else I can mock my dependencies. I'm using webpack+karma+mocha+typescript (ts-loader), all the latest versions available in npm as of today.