babel-loader: Doesn't compile a npm linked module

Not sure this is a babel loader issue or a webpack issue.

I am developing two modules and one of them has a dependency on the other. So I have npm linked it. I have the following config on webpack:

loaders: [{
      test: /\.js$/,
      loaders: ['babel'],
      exclude: /node_modules\/(?!other-module)/,
      include: __dirname
}]

This works fine if I install dependency as a local dependency but if I use npm link, babel doesn’t process it.

Am I missing something? Or is there a workaround for this?

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 117
  • Comments: 72 (2 by maintainers)

Commits related to this issue

Most upvoted comments

@mingxian For Webpack 2, adding this to seemed to resolve (derp) the issue :

resolve: {
	symlinks: false
}

@henit Try putting the presets in your package.json instead with a babel property:

  "babel": {
    "presets": [
      "es2015",
      "react"
    ]
  },

I have the same issue. I have a package at /path/to/project/ and a dependency that worked well when located in /path/to/project/node_modules/dependency/. However, when I npm link the dependency to a git clone located at the same place as the project (/path/to/dependency/, linked via /usr/lib/node_modules the way npm links), then the loaders complain that there is no presets installed in the package directory for dependency when I run webpack from the project directory that npm links to it.

Webpack loader config in /path/to/project:

{
    test: /\.jsx?$/,
    loader: "babel-loader",
    query: {
        presets: ["es2015", "react"]
    }
}

Output from running webpack:

ERROR in ../dependency/index.js
Module build failed: Error: Couldn't find preset "es2015" relative to directory "/path/to/dependency"

How can I make babel-loader use the presets installed in the project the same way it did when the dependency where located inside the project directory instead of looking for it in the linked project directory?

this worked for me, Notice that I don’t have a ‘include’ key.

{
  test: /\.js$/,
  loader: 'babel',
  exclude: /.*node_modules((?!localModule).)*$/,
  query: {
    presets: ['es2015', 'react']
  }
}

It works to me just setting

resolve: {
    symlinks: false
}

Ran into a similar issue as @hitchcott and wound up resolving it with the following:

before (not working)

{
  test: /\.jsx?$/,
  include: [
    path.resolve('./components'),
    path.resolve('./node_modules/common-components')
  ],
  loader: 'babel-loader',
  // query: {} - not specified, deferred to .babelrc
}

after (working)

{
  test: /\.jsx?$/,
  include: [
    path.resolve('./components'),
    fs.realpathSync('./node_modules/common-components') // <-- added realpathSync
  ],
  loader: 'babel-loader',
  // define each presets/plugins in babelrc as absolute to current working directory
  query: {
    presets: [
      'es2015',
      'stage-0',
      'react'
    ].map(dep => require.resolve(`babel-preset-${dep}`)),
    plugins: [
      'transform-runtime',
      'transform-class-properties',
      'transform-decorators-legacy'
    ].map(dep => require.resolve(`babel-plugin-${dep}`))
  }
}

It should be noted, I also have the following:

// add resolve fallback for npm link'd modules
resolve: {fallback: path.join(__dirname, 'node_modules')},
resolveLoader: {fallback: path.join(__dirname, 'node_modules')},

This is happening to me as as well. @barroudjo’s suggested workaround seems to work in my case:

var BABEL_CONFIG = {
    cacheDirectory: true,
    presets: [
        "es2015",
        "stage-0",
        "react"
    ].map(function(name) { return require.resolve("babel-preset-"+name) }),
    plugins: [
        "transform-decorators-legacy"
    ].map(function(name) { return require.resolve("babel-plugin-"+name) })
};

@davidortizfrau’s exclude solution was the only solution here that worked for me: https://github.com/babel/babel-loader/issues/149#issuecomment-226637036

  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /.*node_modules((?!my-npm-linked-module-name).)*$/,
        loaders: ['react-hot', 'babel'],
      },
    ],
  },

I was experiencing the same JSX problem as @hitchcott: https://github.com/babel/babel-loader/issues/149#issuecomment-221946176

I still can’t get this to work. I’ve tried everything in this thread. Seems to be specific with npm linkd react components. ES6 appears to be working, but it’s choking as soon as it gets to jsx including in a linked module.

ERROR in ../myComponent/src/test.js
Module parse failed: /Users/chris/code/myComponent/src/test.js Unexpected token (6:6)
You may need an appropriate loader to handle this file type.
    "babel-core": "^6.9.0",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-react": "^6.5.0",
    "babel-preset-stage-2": "^6.5.0",

Using Webpack 4 I did not use the symlink: false in resolve

but simply had to use

    include: [
      fs.realpathSync('node_modules/component')
    ]

@sebinsua I have tested this a bit now and am pretty sure why it works (at least for me)!

If you have your library at /path/to/linked-package, and your project at /path/to/project. npm linking will link /path/to/project/node_modules/linked-package -> /path/to/linked-package

Let’s say you have the following exclude:

exclude: function(modulePath) {
  return /node_modules/.test(modulePath) && !/node_modules\/linked-package/.test(modulePath)
}

With symlinks: true, modulePath will be /path/to/linked-package. This will not match any of the regexp above as node_modules is not part of the path.

With symlinks: false, modulePath will be /path/to/project/node_modules/linked-package, which does match the regexp.

Hope this helps 😃

I’m a big fan of @seantimm’s solution in #179:

const babelSettings = {
  extends: path.join(__dirname, '/.babelrc')
}

...

loader: 'babel?' + JSON.stringify(babelSettings)

Easier than all those require.resolves and allows you to decouple your settings into the .babelrc again. Just thought I’d add it as another solution until @Swaagie’s PR is merged in Babel

Seeing the same issue, building failed with an npm link module, after removing the link and manually copying over the locally developed module it worked fine. There is something odd with symlinks going on. Webpack config is as follow. Working with node@4.2 and npm@3.5.

'use strict';

const path = require('path');
const ucfirst = require('ucfirst');
const component = path.basename(process.env.COMPONENT);

module.exports = {
  entry: path.join(__dirname, component, 'index.jsx'),
  output: {
    filename: path.join(__dirname, component, 'dist', 'index.js'),
    library: 'UX' + ucfirst(component),
    libraryTarget: 'umd'
  },
  externals: {
    'prismjs': 'Prism',
    'react': 'React',
    'react-dom': 'ReactDOM'
  },
  module: {
    loaders: [{
      test: /\.jsx?$/,
      loader: 'babel-loader?presets[]=react,presets[]=es2015'
    }, {
      test: /\-example\.jsx?$/,
      loader: 'raw-loader'
    }]
  }
};

What should I put in babel.config.js?

Your config is not right. You need to include babel presets in your loader config as well as your babelrc or babelConfig in your package.json. The presets for babel loader can be added with query params or query object. Review the babel 6 docs on presets for your babel config. The preset packages must also be included your project. Currently things are working for me with babel-loader@6.01 with babel presets and babel-core@6.1.4

@mingxian For Webpack 2, adding this to seemed to resolve (derp) the issue :

resolve: {
	symlinks: false
}

@theninja This worked for me, but I’m now wondering why it works. Do you know why it’s working?

What was strange to me, was that even though I had originally made the loader.include point to the resolved location of a symlink, that didn’t work. Then when I got it to just ignore the symlinks using this configuration it started to work…

I’m wondering if this bit of configuration could negatively impact anything, because if it does I’ll want to be able to switch it on/off.

I had the same issue as @hitchcott with an app created with create-react-app and followed @tswaters suggestion, now it works.

First issue

Error in ***/shared-hybrid/src/components/common/Toast.js
Module parse failed: ***\shared-hybrid\src\components\common\Toast.js Unexpected token (15:2)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (15:2)
 @ ./src/components/ObservationPage/ObservationPage.js 42:13-65

I replace in the paths:

appSrc: [resolveApp('src'), resolveApp('node_modules/shared-hybrid/src')],

by

appSrc: [resolveApp('src'), fs.realpathSync(resolveApp('node_modules/shared-hybrid/src'))],

Second issue

Nearly the same kind but not exactly

Error in ***/shared-hybrid/src/components/common/SelectServerDialog.js
Syntax error: ***/shared-hybrid/src/components/common/SelectServerDialog.js: Unexpected token (20:6)

  18 |
  19 |  const dialogActions = [
> 20 |       <FlatButton
     |       ^
  21 |         label="Ok"
  22 |         primary={true}
  23 |         keyboardFocused={true}

 @ ./src/components/HomePage/HomePage.js 54:26-91

I add the following code in the query of babel loader:

        query: {
          presets: [
            'react-app'
          ].map(dep => require.resolve(`babel-preset-${dep}`))
        }

Third issue

Error in ***/shared-hybrid/src/redux/reducers/toast.js
Module not found: 'babel' in ***\shared-hybrid\src\redux\reducers

 @ ***/shared-hybrid/src/redux/reducers/toast.js 12:15-36

Now I set:

resolve: {fallback: paths.appNodeModules}
resolveLoader: {fallback: paths.appNodeModules},

Final issue

It works! … except I commented this section:

preLoaders: [
      {
        test: /\.(js|jsx)$/,
        loader: 'eslint',
        include: paths.appSrc,
      }
    ],

and when I suppress the comments, I get the following errors:

Error in ***/shared-hybrid/src/redux/actions/toast.js

***\shared-hybrid\src\redux\actions\toast.js
  1:1  error  Parsing error: 'import' and 'export' may appear only with 'sourceType: module'

? 1 problem (1 error, 0 warnings)

To fix that, I add in shared-hybrid/package.json:

  "eslintConfig": {
    "extends": "react-app"
  }

And it works! Hope someone will find it usefull 😉

I have the same setup as @Nirazul. Using require.resolve('babel-preset-es2015') instead of 'es2015' resolves this issue for me.

Additionally I set resolve.modulesDirectories: [path.resolve('./node_modules')] so modules in other directories can find modules installed in the main project.

Yea I agree with @Swaagie. The babel-loader fails to find it’s preset modules when the module isn’t under the project root containing the node_modules (even when resolveLoader.root is set). Similar to #166

@janzenz inside LoaderOptionsPlugin. I’ve “fixed” the problem by installing all babel-related plugins in node_modules of linked module.

Added tests to the PR few days ago (finally), I’ll see if I can give someone a nudge.

Thanks @mqklin, this works, but unfortunately when you use multiple loaders you can’t use a query object. Here is the workaround in this case:

var queryObject = {presets: [require.resolve('babel-preset-es2015')]};
var query = require('querystring').stringify(queryObject);
config.loaders = [{
    test: /\.js$/,
    loaders: ['ng-annotate', 'babel?' + query]
}];

I’m pretty sure I’ve got those modules installed and it’s not about the presets, babel-loader simply fails with symlinks

The issue here is most likely that .babelrc files in Babel 6 only apply to files nested in folders within the .babelrc’s folder. This means the only option in Babel 6 is to put the configuration directly in your webpack.config.js.

As of Babel 7, this is restricted even further, and .babelrc files will also not apply to child packages, but we’ve also added support for project-wide babel.config.js files, so I’d recommend users who wish to compile node_modules and linked packages use those.

Just want to give my 2 cents as well, at first the symlinks: false didn’t work for me.

After some more digging i saw that in the resolve section i had the following setup:

resolve: {
        alias: {
             someAlias: 'alias path'
        },
        modules: [
            path.resolve(__dirname, '/app'),
            path.resolve('node_modules')
        ],
        symlinks: false
}

turns out that there is a big difference between absolute path loading in modules in the resolve section vs relative loading see this article: https://webpack.js.org/configuration/resolve/#resolve-modules

So with that setup it failed my build since it couldn’t find the actual files in my symlinked directory.

After changing it to:

resolve: {
        alias: {
             someAlias: 'alias path'
        },
        modules: [
            path.resolve(__dirname, '/app'),
            'node_modules'
        ],
        symlinks: false
}

it worked perfectly for me, even my webpack dev server picks up the changes and refreshes what it needs to do.

Maybe it helps for someone who runs into this.

The reason why i’m adding my app to resolve modules is so i can reference pretty much anything from root level, so in my bundles i don’t have to do:

…/…/someModule/index.js

instead i can just simply:

someModule/index.js

in case someone is wondering why i use that setting

I had the same issue using an ejected react app. However, it seems to be working when I include the realpath instead of the absolute path to the target dependency.

{
  test: /\.(js|jsx|mjs)$/,
  include: [paths.appSrc, fs.realpathSync('node_modules/package')],
  loader: require.resolve('babel-loader'),
  ...

On webpack 3 with yarn workspaces,

javascript resolve: { symlinks: false }

made things click. nice

@joaoreynolds We’re not using resolve.modules in these two projects, for whatever that is worth. Nor do we have Webpack or Babel setup or packaged in the symlinked deps. Outer configs look roughly like:

  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules\/(?!symlinked_lib)/,
        loader: "babel-loader",
        query: {
          cacheDirectory: true,
          presets: ["env", "react"]
        }
      },
     [etc]
  },
  [etc]
  resolve: {
    alias: {
      "foo": Path.resolve(__dirname, "src/foo"),
      [etc]
    },
    extensions: [".js", ".jsx", ".scss"],
    symlinks: false
  }

But, I doubt that makes a difference. Anyway, my thought was just that --watch can work with symbolic links in node_modules and symlink === false, so I don’t think the root cause for you is as straightforward as it might seem.

Update, the only way I could get watching (of any kind) with npm-link to work was have symlinks: true and I had to install all babel presets and plugins in the linked dependency module - which seems weird because the linked dependency doesn’t even have it’s own webpack setup.

Maybe I’m the only one in thinking that if symlinks: true (default webpack behavior), then my project should look for plugins and presets in the current directory’s node_modules. (just the same way it loads babel-loader which is NOT required to be installed in the linked-module)

{symlinks: false} fixes the error, however it disrupts webpack’s watch function (and therefore webpack-dev-server will not recompile when changing files in the “linked” dependency).

Anybody found another solution?

@henit

I just found a method in the Babel docs. If you need a preset or plugin locally/bundled in your project like I did you can provide the plugins and presets a relative or absolute path to the node module package.

    const Babel = require('babel-core');

    const options = {
        code: true,
        presets: [
            ['./node_modules/babel-preset-es2015', {
                'modules': false,
                'loose': false,
                'spec': true
            }]
        ]
    };

    const result = Babel.transform(text, options);

Notice the preset name instead of es2015 it is a relative path ./node_modules/babel-preset-es2015.

TIP: You also might need to resolve to the absolute path in which case you can use Path.join(__dirname, './relative/path/package');

Adding an include to fs.realpathSync(__dirname + '/node_modules/my-repo/lib') might also be another solution thanks to https://github.com/webpack/webpack/issues/1643#issuecomment-220484356.

Chiming in on this thread as I’ve got a variant scenario (I think). I’ve developing a CLI tool that functions like a black box site generator. My intent is that the tool be installed globally and executed from the command line where it locates a containing package.json relative to cwd() that it parses to retrieve some options. Subsequently, the contents of the target project (i.e. the dir structure rooted at the directory where package.json was found) is processed into a site, and the generated resources are written back out to the target project directory structure.

Internal to the tool, I synthesize webpack configuration objects and delegate to webpack which in turn delegates to babel-loader, babel … and fails when resolving the es2015 and react presets as described here (for slightly different reasons than typical but same root cause I think).

Currently, webpack and babel loader are correctly resolved in the node_modules dir of my tool where they’re declared as regular dependencies in the tool’s package.json. This isn’t a global install of anything more than a random Node.js derived CLI tool that happens to depend on webpack, babel… as an implementation detail.

So although I agree with the rationale of wanting to install babel on a per-project basis, this sharp edge with presets makes it difficult to embed in automation scripts.

For now I’ve modifying the dev-dependencies of the target project package.json and going to force an npm install prior to the first invocation of webpack (occurs as one of many steps in the site generation process encapsulated by the tool). But I think this extra complexity is not really necessary.

This thread: https://discuss.babeljs.io/t/error-parsing-jsx-with-global-installation-babel-preset-react/59/14 on Babel discussion is fairly recent and seems related.

Anyone know how to get this sort of wholly encapsulated embedding to work elegantly? Thanks.