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

Babel config without .babelrc #1468

Closed
ccorcos opened this issue Aug 19, 2016 · 36 comments
Closed

Babel config without .babelrc #1468

ccorcos opened this issue Aug 19, 2016 · 36 comments

Comments

@ccorcos
Copy link

ccorcos commented Aug 19, 2016

I'm building a modular build system that ideally is going to be its own npm package soon enough. However right now, its just a parallel directory:

/build-system
/project
  /src

One of the goals is to separate out all the build dependencies. I want to everything to just work, so I'm not using a .babelrc file and instead linking to babel presets inside the webpack config:

    babel: {
      presets: [
        'babel-preset-es2015',
        'babel-preset-react',
        'babel-preset-stage-0',
      ].map(require.resolve),
    },

So I was hoping that somehow I could configure and run Jest on the a targeted directory without specified configurations that doesnt rely on directory structure.

@ccorcos
Copy link
Author

ccorcos commented Aug 19, 2016

Would it be possible to get a config here to set these babel options? What do you think would be the best practice for this?

@gnoff
Copy link

gnoff commented Aug 19, 2016

Would love to know the answer on this here as well

@cpojer
Copy link
Member

cpojer commented Aug 19, 2016

You can wrap babel-jest and create your own preprocessor by just passing in the options you want. That should be sufficient.

On Fri, Aug 19, 2016 at 9:04 PM +0200, "Josh Story" <[email protected]mailto:[email protected]> wrote:

Would love to know the answer on this here as well

You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHubhttps://github.com//issues/1468#issuecomment-241106867, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AAA0KEhHwHWLdV4HMHF4-ck3CRpb7QsQks5qhf4VgaJpZM4Joskp.

@ccorcos
Copy link
Author

ccorcos commented Aug 19, 2016

and how do I get that into jest?

@aaronabramov
Copy link
Contributor

@ccorcos you can specify your custom preprocessor in the config
http://facebook.github.io/jest/docs/api.html#scriptpreprocessor-string

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

@DmitriiAbramov I'm in a similar situation. Working on a modular build system and need to be able to pass in some runtime configuration to the scriptPreprocessor. But since scriptPreprocessor is expected as a string, it's impossible to pass in any runtime configuration without resorting to some global shared state mutation.

If, instead, it were to accept a string or function I think that would solve the problem, but I'm not sure if that would be incompatible with some of Jest's internals? If not, I'd be happy to submit a PR to allow for this. Please advise.

Thanks!

@cpojer
Copy link
Member

cpojer commented Sep 11, 2016

You can make your own preprocessor that simply does this:

require('babel-jest').createTransformer(babelOptions)

and then point scriptPreprocessor to that file.

See https://github.com/facebook/jest/blob/master/packages/babel-jest/src/index.js#L53

This makes the simple case simple and the hard case possible. I don't think we'll make scriptPreprocessor a function and the current solution should work well enough.

@tizmagik
Copy link

@cpojer thanks for taking the time to reply! Sorry but maybe I'm not doing a good job of explaining what's happening. So, what you have suggested is what I am doing. However, I don't see a way of passing in (at run-time) parameters that would affect babelOptions.

/* jestConfig.js */
module.exports = {
  ...
  scriptPreprocessor: path.resolve('./myCustomPreprocessor.js') // no way to pass in runtime options!
  ...
};
/* myCustomPreprocessor.js */
// pseudo-code, this doesn't actually work obviously
module.exports = (options /* <-- how to specify?? */) => {
  const babelOptions = options; // assuming some merging, conditional logic, etc.

  return babelJest.createTransformer(babelOptions);
}
$ npm run testScript --optionThatAffectsBabelOptions

Assuming testScript is what eventually runs Jest, I don't see a way of passing in optionThatAffectsBabelOptions. Maybe modifying some global shared state or using a singleton pattern and requiring the preprocessor early in the run-time (although I'm not sure that would even work since I believe Jest spawns threads right?). Any ideas? I'd be happy to create a sample repo to help illustrate the problem more if it's still unclear. Or maybe I'm just missing something obvious (possible!). Thanks for your help.

@tizmagik
Copy link

@ccorcos right, but notice that CRA is not passing in any runtime options. It's all just loading static files.

@cpojer
Copy link
Member

cpojer commented Sep 12, 2016

I don't understand what you are saying with "runtime". The preprocessor is required by Jest and is used to transform files. When it is being required you can do whatever you want to create your babel options object that you pass on to createTransfomer. I'm pretty sure there is just some confusion here because this API gives you all the power you need and if it's not enough then there is some confusion left.

@aaronabramov
Copy link
Contributor

@tizmagik as i understand you're trying to pass a parameter from your build process (e.g. grunt or gulp pipeline) into jest transform, so that jest transformation is affected by outer process?
as @cpojer said, you can do anything inside your custom preprocessor, but if you really need to pass some external parameters you can probably use environment variables.
e.g.

spawnProcess('jest', {env: {ADD_CUSTOM_TRANSFORMATION: 1}});

then in your customPreprocessor:

if (process.env.ADD_CUSTOM_TRANSFORMATION === 1) {
  plugins.push(require('my-custom-transformation-plugin');
}

@ccorcos
Copy link
Author

ccorcos commented Sep 12, 2016

Ah yes, I see what you mean @tizmagik. You want to be able to something like this:

    scriptPreprocessor: require('config/jest/transform.js')({development: true, some_feature: false}),

but all we have is this:

    scriptPreprocessor: path.resolve('config/jest/transform.js'),

@tizmagik
Copy link

@ccorcos yes, exactly!

What @DmitriiAbramov suggested would work, but I would rather avoid having to simulate/affect some global state.

Would you accept a PR for this change?

@cpojer
Copy link
Member

cpojer commented Sep 12, 2016

I still don't fully follow here. If you pass in the options to the scriptPreprocessor from the outside I see no problem to instead create them from the inside. Just factor out the piece of code that creates your babel config:

// babelConfig.js
module.exports = {babel options};

// transformer
module.exports = babelJest.createTransformer(require('./babelConfig'));

// other build scripts
require('./babelConfig');

we are using this approach all over at FB and it works well for us.

@ccorcos
Copy link
Author

ccorcos commented Sep 12, 2016

Yeah but that not a runtime configuration.
On Mon, Sep 12, 2016 at 11:53 Christoph Pojer [email protected]
wrote:

I still don't fully follow here. If you pass in the options to the
scriptPreprocessor from the outside I see no problem to instead create them
from the inside. Just factor out the piece of code that creates your babel
config:

// babelConfig.js
module.exports = {babel options};

// transformer
module.exports = babelJest.createTransformer(require('./babelConfig'));

// other build scripts
require('./babelConfig');

we are using this approach all over at FB and it works well for us.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#1468 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ABth342ubUzRMvRHVHW3IiJtvleGMhKqks5qpZ_CgaJpZM4Joskp
.

@aaronabramov
Copy link
Contributor

@tizmagik i think the biggest thing here is being able to pass config to child processes, when we run tests in parallel.
I don't think there's an easy way to pass a function to another process right now, but i might be wrong.

see https://github.com/facebook/jest/blob/master/packages/jest-cli/src/TestRunner.js#L255

@ccorcos
Copy link
Author

ccorcos commented Sep 12, 2016

oh interesting. you accept only file path for parallelism... seems like we should be able to solve this issue if the args are serializable. then we have something kind of like how webpack loaders work:

{
  loader: 'babel',
  query: {
    presets: ['es2015'],
  }
}

@cpojer
Copy link
Member

cpojer commented Sep 12, 2016

@ccorcos can you show a real example of why my proposal won't work? In practice it worked really well for us. It requires you to only invert a few things and will probably lead to a better separation of concerns.

@ccorcos
Copy link
Author

ccorcos commented Sep 12, 2016

Here's a pseudocode example:

const buildOptions = parseCliArgs(process.argv)
const babelConfig = generateBabelConfig(buildOptions)
runJest(babelConfig)

The buildOptions are not static -- they're generated at runtime based. runJest doesnt accept a babel config, but to a file that exports a babel config. So perhaps a solution would be to write the babelConfig to a file in /tmp and point to it with this scriptProcessor option, but thats certainly hacky.

@cpojer
Copy link
Member

cpojer commented Sep 12, 2016

Why do your build options change so much and why are they generated? I feel like there must be a simpler way with more static build configuration that's stored in a file somewhere.

@ccorcos
Copy link
Author

ccorcos commented Sep 12, 2016

because I have a complicated (legacy) build system and have to support several different ways of doing things.

@nfarina
Copy link
Contributor

nfarina commented Feb 1, 2017

For those landing on this issue and wondering what scriptPreprocessor is (it's since been removed), here is how I got jest to work with babel without using .babelrc.

./jest.transform.js

// Custom Jest transform implementation that wraps babel-jest and injects our
// babel presets, so we don't have to use .babelrc.

module.exports = require('babel-jest').createTransformer({
  presets: ['node7', 'react', 'stage-2'], // or whatever
});

./jest.config.json (or "jest" entry in package.json):

"transform": {
  "^.+\\.js$": "<rootDir>/jest.transform.js"
},

Now run jest with these options:

jest --config jest.config.json --no-cache

The --no-cache option bit me hard - when you're messing with transforms, jest will often skip your custom transformer entirely if it thinks it's already transformed it. Once you have things working smoothly, you can drop --no-cache.

@alfonsodev
Copy link

alfonsodev commented Feb 28, 2017

Other option could be to keep .babelrc and use webpack babel-loader babelrc option to true.
Then at least you can share babel settings in one place,
or if .babelrc is not an option, here is an example based on @nfarina code,
but reading the babel options directly from your webpack.config.js.
Of course the webpackConfig.module.rules[1].query is ugly and should be replaced by a function that iterates your rules and finds loader: 'babel-loader', to return query property.

var webpackConfig = require('../../webpack.config.js')
var babelConfig = webpackConfig.module.rules[1].query
module.exports = require('babel-jest').createTransformer(babelConfig)

@foxdoubt
Copy link

foxdoubt commented Jan 9, 2018

@nfarina thank you so much for your solution above. I struggled for hours this morning trying to get Jest to recognize import statements in a setup using Webpack and babel-loader, but no .babelrc.

@sparkbuzz
Copy link

sparkbuzz commented Jan 26, 2018

Trying the solution posted by @nfarina, but it appears as if Jest is ignoring my transform. I even tried manually deleting the Jest cache with --clearCache.

@foxdoubt
Copy link

foxdoubt commented Jan 29, 2018

@sparkbuzz not sure if you're still blocked, but here's how I used @nfarina 's idea:

jest.config.js

module.exports = {
  verbose: true,
  transform: { '^.+\\.js$': '<rootDir>/jestPreprocess.js' },
};

jestPreprocess.js

const babelOptions = { presets: ['env'] };

module.exports = require('babel-jest').createTransformer(babelOptions);

package.json

"scripts": {
    "test": "jest --config jest.config.js --no-cache",
    // ... 

Hope that helps or sparks a solution for you. The issue might be that you're using --clearCache instead of --no-cache.

@edmorley
Copy link
Contributor

For anyone stuck on this - the approach we took was:

// transformer.js
const { util, transform } = require('babel-core');

module.exports = {
  process(src, filename, config) {
    return util.canCompile(filename) ?
      transform(src, Object.assign({}, { filename }, config.globals.BABEL_OPTIONS)) :
      src;
  }
};

And then pass BABEL_OPTIONS to runCLI via globals.

But beware we also had to stringify the options passed to runCLI or they were silently ignored (see #5429).

@jharris4
Copy link

I have a use case to be able to call runCLI(config) where config contains inline transforms (not required from a separate file).

To make this work I opened the following PR: #7398

@onlywei
Copy link

onlywei commented Jan 9, 2019

Sorry for reviving this thread, but I would really like to see an official documented way to provide babel options to jest outside of .babelrc. Both Webpack and Rollup both have the ability to do this, and it makes a lot of sense to me because I very often use different babel options for my bundling vs my unit-testing.

@jharris4
Copy link

jharris4 commented Jan 10, 2019

@onlywei you don't have to use .babelrc to configure jest to work with babel.

You can setup a custom transform in your jest config that can do whatever you want (with the caveat that it has to point to a file or package that does the transform).

If you provide a little more information about what you're trying to do I can try and be a little more specific in suggesting a solution... ;-)

@SimenB
Copy link
Member

SimenB commented Jan 10, 2019

Another possibility (if using Jest 24, currently in beta) is to check if caller.name === 'babel-jest' || caller.name === 'jest-runtime' in your babel.config.js. See https://babeljs.io/docs/en/config-files#apicallercb

If you really don't want a config file, using createTransformer from babel-jest is the way to go, see https://jestjs.io/docs/en/tutorial-react#custom-transformers

@stevenvachon

This comment has been minimized.

@lu-zen

This comment has been minimized.

@kaorukobo
Copy link

kaorukobo commented Jan 7, 2020

@SimenB
Another possibility (if using Jest 24, currently in beta) is to check if caller.name === 'babel-jest' || caller.name === 'jest-runtime' in your babel.config.js. See https://babeljs.io/docs/en/config-files#apicallercb

Thanks.
I could not get the same way as caller (exactly, api.caller((caller) => caller.name)) was undefined.
But I've solved the problem as follows:

// babel.config.js
module.exports = function (api) {
    const isJestEnv = api.env() === 'test'

    // This line is required AFTER any calls of api's method.
    api.cache.forever()

    // Nothing changed? TRY `jest --no-cache` as @nfarina mentioned above.

    return {
        presets: [
            '@vue/app'
        ],
        plugins: (
            // Activate the require-context-hook plugin at the only Jest env,
            // to avoid `fs.readdirSync is not defined` error
            // on the Webpack-generated code.
            isJestEnv ? [
                'require-context-hook'
            ] : []
        )
    }
}

@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 11, 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