Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Running Jest with Webpack, Babel, TypeScript and CSS Modules #1466

Closed
avanderhoorn opened this issue Aug 19, 2016 · 8 comments
Closed

Running Jest with Webpack, Babel, TypeScript and CSS Modules #1466

avanderhoorn opened this issue Aug 19, 2016 · 8 comments

Comments

@avanderhoorn
Copy link

I'm trying to get the configuration right for using Jest with my current setup. I'm using Webpack for everything at the moment and my loaders look something like following

// webpack.config.js
...
{ test: /\.jsx?$/, loaders: [ 'babel' ], exclude: /node_modules/, include: __dirname },
{ test: /\.tsx?$/, loaders: [ 'babel', 'ts' ], include: __dirname },
...

In the above you will notice that my .tsx?? files are being run through ts first and then through babel. I'm doing this because I have my tsconfig.json setup to target es6 and preserve jsx so that Babel can do the rest of the transformation and hence have access to the Babel plugin ecosystem.

// tsconfig.json
{
    "compilerOptions": {
        "module": "es6",
        "target": "es6",
        "moduleResolution": "node",
        "allowSyntheticDefaultImports": true,
        "jsx": "preserve",
        "sourceMap": true,
        "outDir": "./dist/"
    },
    ...
}

Looking around, it doesn't appear that this is the happy path when using Jest and I'm thinking that I probably need a scriptPreprocessor to do the required transformation. It's a little bit of a pain that I have already defined my "build pipeline" once and I have to manually code it in the scriptPreprocessor but I understand that given the incremental nature of Jest and the "single" output nature of webpack, that the scriptPreprocessor is going to be needed... am I thinking about that right?

Assuming this is all correct, I had a shot of writing a scriptPreprocessor that looks like the following:

// preprocessor.js
const tsc = require('typescript');
const tsConfig = require('./tsconfig.json');
const babelJest = require('babel-jest');

module.exports = {
    process(src, path) {
        const isTypeScript = path.endsWith('.ts') || path.endsWith('.tsx');

        if (isTypeScript) {
            src = tsc.transpileModule(
                src,
                {
                    compilerOptions: tsConfig.compilerOptions,
                    fileName: path
                }
            );
        }
        if (path.endsWith('.js') || path.endsWith('.jsx') || isTypeScript) {
            src = babelJest.process(src, path);
        }
        return src;
    },
};

Unfortunately it doesn't seem that Jest is picking up my scriptPreprocessor (assuming that the logic in the above is roughly right). The following is what I have in my package.json:

// package.json
{
  ...
  "jest": {
    "scriptPreprocessor": "<rootDir>/preprocessor.js",
    "moduleFileExtensions": [ "ts", "tsx", "js", "jsx" ],
    "testFileExtensions": [ "ts", "tsx", "js", "jsx" ],
    "moduleDirectories": [ "node_modules" ],
    "unmockedModulePathPatterns": [ "./node_modules/lodash" ]
  }
}

Now when I run Jest, its coming back with unexpected token errors which make me believe that "skeleton" of the test (as to just get this up and running I have an empty test written in TS) isn't being transformed by the scriptPreprocessor.

Any ideas why the scriptPreprocessor isn't being detected and any thoughts on the overall approach and if there is a better way?

@cpojer
Copy link
Member

cpojer commented Aug 29, 2016

I recommend running Jest with --no-cache while working on your own preprocessor but yes, the way you are doing this seems fine.

@cpojer cpojer closed this as completed Aug 29, 2016
@Bnaya
Copy link

Bnaya commented Dec 27, 2016

Hey @avanderhoorn , have you managed to make it work? i'm about to do something similar

@avanderhoorn
Copy link
Author

@Bnaya my jest.preprocessor.js looks like this:

const tsc = require('typescript');
const babelJest = require('babel-jest');

module.exports = {
    process(src, path) {
        const isTs = path.endsWith('.ts');
        const isTsx = path.endsWith('.tsx');

        if (isTs || isTsx) {
            src = tsc.transpileModule(
                src,
                {
                    compilerOptions: {
                        'module': tsc.ModuleKind.ES6,
                        'target': tsc.ScriptTarget.ES6,
                        'moduleResolution': tsc.ModuleResolutionKind.Node,
                        'allowSyntheticDefaultImports': true,
                        'jsx': tsc.JsxEmit.Preserve,
                        'sourceMap': true,
                        'outDir': './dist/'
                    },
                    fileName: path
                }
            );
            src = src.outputText;

            // update the path so babel can try and process the output
            path = path.substr(0, path.lastIndexOf('.')) + (isTs ? '.js' : '.jsx') || path;
        }

        if (path.endsWith('.js') || path.endsWith('.jsx')) {
            src = babelJest.process(src, path);
        }
        return src;
    },
};

my project.json jest setup looks like:

  "jest": {
    "scriptPreprocessor": "<rootDir>/test/config/jest.preprocessor.js",
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js",
      "jsx"
    ],
    "testRegex": "(\\.(test|spec))\\.(ts|tsx|js|jsx)$",
    "moduleDirectories": [
      "node_modules"
    ],
    "moduleNameMapper": {
      "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)$": "<rootDir>/test/mocks/fileMock.js",
      "^.+\\.(css|less|scss)$": "identity-obj-proxy"
    },
    "globals": {
      "DEVELOPMENT": false,
      "FAKE_SERVER": false
    }
  }

Let me know if I'm missing something that would be useful and I'll post it.

@lpcarignan
Copy link

@avanderhoorn You code sample in jest.preprocessor.js saved me. I was also doing React in Typescript => Babel => Jest. It never occured to me that babel.process() would check the file extension. I was passing the original path with the .tsx extension and Babel didn't do anything.

The following lines solved my problem:

// update the path so babel can try and process the output
path = path.substr(0, path.lastIndexOf('.')) + (isTs ? '.js' : '.jsx') || path;

@avanderhoorn
Copy link
Author

Ya that stumped me as well... I ended up splunking through the code with a bunch of console.logs and I figured that one out, hence part of the reason for the comment. Really glad it helped someone else.

@curioussavage
Copy link

curioussavage commented May 18, 2017

@avanderhoorn are you using lodash? For me this breaks our lodash code.

We get TypeError: values_1.default is not a function etc

We have a lot of lodash usage in our project. we import it like so.

import has from 'lodash/has'

babelrc:

{
  "presets": [
    ["es2015", { "modules": false }], 
    "stage-2",
    "react"
  ],
  "plugins": [
    "lodash", // I tried removing this plugin (has no effect)
    "transform-class-properties",
    "transform-react-constant-elements",
    "transform-react-inline-elements"
  ]
}

We are normally using webpack with babel loader, where this works. I import our babel rc and set modules to 'commonjs' for the es2015 preset and then make a transformer like so:

const babelJest = require('babel-jest').createTransformer(babelConfig);

I know this might be more of an es2015 modeuls/ imports kind of question but I can't seem to find anywhere else people are discussing the usage of all these tools together.

src output example:

    'use strict';var _index = require('./index');var _index2 = _interopRequireDefault(_index);
    var _comment = require('app/actions/comment');var commentActions = _interopRequireWildcard(_comment);
    var _createTest = require('lib/createTest');var _createTest2 = _interopRequireDefault(_createTest);
    var _has = require('lodash/has');var _has2 = _interopRequireDefault(_has);function _interopRequireWildcard(obj) {if (obj && obj.__esModule) {return obj;} else {var newObj = {};if (obj != null) {for (var key in obj) {if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];}}newObj.default = obj;return newObj;}}function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _defineProperty(obj, key, value) {if (key in obj) {Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });} else {obj[key] = value;}return obj;}

    (0, _createTest2.default)({ reducers: { collapsed: _index2.default } }, function (_ref) {var getStore = _ref.getStore,expect = _ref.expect;
      describe('REDUCER - comments.collapsed', function () {
        it('should alter the collapse state of a comment', function () {
          var id = 'foo';var _getStore =
          getStore(),store = _getStore.store;
          expect(store.getState().collapsed).to.eql({});
          (0, _has2.default)({ 'bar': 1 }, 'bar');

          store.dispatch(commentActions.commentCollapseToggled({ id: id }));
          expect(store.getState().collapsed).to.eql(_defineProperty({}, id, true));

          store.dispatch(commentActions.commentCollapseToggled({ id: id }));
          expect(store.getState().collapsed).to.eql(_defineProperty({}, id, false));
        });
      });
    });

@IgnusG
Copy link

IgnusG commented Oct 23, 2017

@curioussavage Not sure if you ever solved this but for anyone who stumbles upon this using ts-jest with the useBabelrc: true option works the same as the above preprocessor and should counter the TypeError you experienced

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 13, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants