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 · 28 comments

Comments

Projects
None yet
10 participants
@ccorcos

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

This comment has been minimized.

Show comment
Hide comment
@ccorcos

ccorcos 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?

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

This comment has been minimized.

Show comment
Hide comment
@gnoff

gnoff Aug 19, 2016

Would love to know the answer on this here as well

gnoff commented Aug 19, 2016

Would love to know the answer on this here as well

@cpojer

This comment has been minimized.

Show comment
Hide comment
@cpojer

cpojer Aug 19, 2016

Contributor

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" <notifications@github.commailto:notifications@github.com> 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.

Contributor

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" <notifications@github.commailto:notifications@github.com> 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

This comment has been minimized.

Show comment
Hide comment
@ccorcos

ccorcos Aug 19, 2016

and how do I get that into jest?

ccorcos commented Aug 19, 2016

and how do I get that into jest?

@aaronabramov

This comment has been minimized.

Show comment
Hide comment
@aaronabramov
Member

aaronabramov commented Aug 19, 2016

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

@cpojer cpojer closed this Aug 29, 2016

@tizmagik

This comment has been minimized.

Show comment
Hide comment
@tizmagik

tizmagik Sep 11, 2016

@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!

tizmagik commented Sep 11, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@cpojer

cpojer Sep 11, 2016

Contributor

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.

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@tizmagik

tizmagik Sep 12, 2016

@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 commented Sep 12, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@tizmagik

tizmagik Sep 12, 2016

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

tizmagik commented Sep 12, 2016

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

@cpojer

This comment has been minimized.

Show comment
Hide comment
@cpojer

cpojer Sep 12, 2016

Contributor

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.

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@aaronabramov

aaronabramov Sep 12, 2016

Member

@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');
}
Member

aaronabramov commented Sep 12, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@ccorcos

ccorcos 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'),

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

This comment has been minimized.

Show comment
Hide comment
@tizmagik

tizmagik Sep 12, 2016

@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?

tizmagik commented Sep 12, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@cpojer

cpojer Sep 12, 2016

Contributor

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.

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@ccorcos

ccorcos Sep 12, 2016

Yeah but that not a runtime configuration.
On Mon, Sep 12, 2016 at 11:53 Christoph Pojer notifications@github.com
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
.

ccorcos commented Sep 12, 2016

Yeah but that not a runtime configuration.
On Mon, Sep 12, 2016 at 11:53 Christoph Pojer notifications@github.com
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

This comment has been minimized.

Show comment
Hide comment
@aaronabramov

aaronabramov Sep 12, 2016

Member

@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

Member

aaronabramov commented Sep 12, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@ccorcos

ccorcos 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'],
  }
}

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

This comment has been minimized.

Show comment
Hide comment
@cpojer

cpojer Sep 12, 2016

Contributor

@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.

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@ccorcos

ccorcos 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.

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

This comment has been minimized.

Show comment
Hide comment
@cpojer

cpojer Sep 12, 2016

Contributor

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.

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@ccorcos

ccorcos Sep 12, 2016

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

ccorcos commented Sep 12, 2016

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

@nfarina

This comment has been minimized.

Show comment
Hide comment
@nfarina

nfarina Feb 1, 2017

Contributor

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.

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

This comment has been minimized.

Show comment
Hide comment
@alfonsodev

alfonsodev 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)

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

This comment has been minimized.

Show comment
Hide comment
@foxdoubt

foxdoubt 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.

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

This comment has been minimized.

Show comment
Hide comment
@sparkbuzz

sparkbuzz 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.

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

This comment has been minimized.

Show comment
Hide comment
@foxdoubt

foxdoubt 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.

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

This comment has been minimized.

Show comment
Hide comment
@edmorley

edmorley Apr 27, 2018

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).

Contributor

edmorley commented Apr 27, 2018

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment