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

Having trouble importing deepmerge with Webpack #87

Closed
GioSensation opened this issue Nov 29, 2017 · 41 comments
Closed

Having trouble importing deepmerge with Webpack #87

GioSensation opened this issue Nov 29, 2017 · 41 comments

Comments

@GioSensation
Copy link

When importing deepmerge with import * as merge from 'deepmerge'; throws Error: merge is not a function in the app, while the tests run just fine.

When importing with import merge from deepmerge;, the app works fine, but the jest tests throw TypeError: deepmerge_1.default is not a function`.

I have temporarily patched the issue in my code with an if/else block to reassign the correct function to the variable, but I feel there must be a better way.

Maybe jest is misconfigured. Has anyone ever encountered a similar issue or can provide a configuration template that is proven to be working?

This describes a similar issue to what I am experiencing aurelia/skeleton-navigation#606, though I am not using Aurelia, nor Moment. It just seems like the same kind of problem.


Node version: 8.6.0
NPM version: 5.5.1
Webpack version: 3.6.0
Typescript version: 2.5.3
deepmerge version: 2.0.1


tsconfig.json

{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": false,
    "module": "commonjs",
    "target": "es5",
    "lib": ["es7", "es2017", "dom"],
    "jsx": "preserve",
    "allowJs": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.test.ts"]
}

.babelrc

{
  "presets": ["env", "react"],
  "plugins": [
    "react-css-modules"
  ]
}

jest.config.json

{
  "transform": {
    ".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
  },
  "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
  "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json"]
}
@TehShrike
Copy link
Owner

import * as merge is incorrect - assuming your bundler is picking up the ES module, then you're loading the ESM build of the package, which only exposes a default export. import merge from 'deepmerge' is correct.

I would guess that there is different bundling/module loading happening between typescript and jest. I'm not familiar with either of them, but my clue to you is to make sure that they're both either paying attention to the module field in deepmerge's package.json (if you want them to pick up the ESM version), or they're both ignoring it (if you want them to pick up the UMD version).

@richardschneider
Copy link

I'm not using TypeScript, just JS on the browser and on node. v2.0.2 works on node, but fails with the error on a browser. Reverting to v1.5.2 works on both environments.

@TehShrike
Copy link
Owner

@richardschneider What bundler are you using? What is the error message?

@richardschneider
Copy link

@TehShrike I'm using webpack. The error is TypeError: deepmerge is not a function.

See code at https://github.com/richardschneider/ipfs-encryption/blob/master/src/keychain.js#L80

@TehShrike
Copy link
Owner

I couldn't find any webpack config in that repo, but I'm no Webpack expert anyways.

In any case, I can guess at the problem pretty easily - your Webpack is configured to load in the ESM module, and is probably exposing default exports as a default property instead of the object you get when you require.

There are lots of ways you could fix this - you could const deepmerge = require('deepmerge').default, or you could switch your project over to using ES modules and do import deepmerge from 'deepmerge', or you could configure Webpack to ignore the module setting in package.json and just pull in main, since it looks like you don't care about ES modules, or you could configure Webpack to return the default export as the object returned by require instead of returning { default: deepmerge }.

If you want your un-built code to work in node.js, I'm guessing you'll want to do one of the two latter options.

@GioSensation
Copy link
Author

GioSensation commented Dec 8, 2017

Just to update the issue, I have solved this by importing explicitly the file I want, es.js, like this:

import merge from '../node_modules/deepmerge/dist/es.js';

I am not particularly happy about this, because if the name or path to the file change, the import will break. It is still the best solution I could find.

I have asked a question on StackOverflow to see if anybody had any insights to offer, but no answers thus far.

I guess you can close this issue if you have nothing to add.

Thanks.

@TehShrike
Copy link
Owner

@GioSensation You still haven't responded to my earlier message - it looks like you have two bundler environments, and they're both resolving modules differently. This doesn't have anything to do with this package using Rollup to bundle, it has to do with your bundlers respecting the main vs module fields in package.json.

A quick search of issues in the Jest repo turns up this: jestjs/jest#2702

It looks like somebody in that thread figured out a workaround for Jest.

@GioSensation
Copy link
Author

Thanks. I will look into it and wait for the Jest team to integrate that pull request.

Cheers!

@Tarnadas
Copy link

Could this be reopenend?
The problem still persists and I don't feel like const merge = require('deepmerge').default is an ideal solution.
My app won't compile when I use import merge from 'deepmerge' with TypeScript 2.8 and Webpack 3.10.0, so it's not just affecting jest tests.

@TehShrike
Copy link
Owner

As long as your bundler respects the module field in package.json (which webpack claims to support), everything should work fine.

The package.json's module field points to es.js, which is a traditional ES module that exports a single default.

The package.json's main field points to umd.js, which is a bog-standard UMD file compatible with CommonJS and AMD bundlers.

Which one of those do you want your bundler to pick up? That is up to your project's configuration.

As far as I know, deepmerge's two entry points are the epitome of bundler-friendliness. Any issues should be the result of a misconfiguration or a bug in the bundler that you're using.

I don't know if it's a configuration issue or a bug, but it sounds like some combination of Typescript + Webpack has an issue out of the box.

@leoalves
Copy link

leoalves commented Feb 1, 2018

@Tarnadas
The issue with typescript is that the types are wrong.

If you edit the file index.d.ts from node_modules/types/deepmerge and change the export from
export = deepmerge; to export default deepmerge you can import it as import deepmerge from 'deepmerge'

If you want to import as import merge from 'deepmerge' you need to replace all references in that file from deepmerge to merge.

I am not sure if the typings reflect the most up to date version of the library. There is only one method that I use and for that one is working.

@TehShrike
Copy link
Owner

ooooh, is that why people are having difficulty with TypeScript?

Where are people getting these types from? Can somebody update them?

Does Typescript pull from module by default?

@leoalves
Copy link

leoalves commented Feb 1, 2018

There is a single repository that most common libraries types are there. It's mostly updated and maintainted by the community. https://github.com/DefinitelyTyped/DefinitelyTyped. The issue is that a lot of times they are not up to date with the library and this can cause issues when compiling.

In this case the problem is more with how TypeScript import works. TypeScript threats CommonJS/AMD/UMD modules in the same way as ES6 modules. When you do import * as merge from 'deepmerge' is the same as merge = require('deepmerge'). This doesn't work for your library.

The other way would be import merge from 'deepmerge' which is the same as merge = require('deepmerge').default. This would work for your library, but the typescript compiler has a configuration option called noImplicitAny. If this option is enabled the typescript does not compile if the type is any. That's the case for your library since you don't have typings.

This option is not enabled by default, but since @Tarnadas was complaining about compiling issues, I figured that this was enabled for him. I also assumed he was using the types from DefinelyTyped. That's why I suggested changing that file.

@pacey
Copy link

pacey commented Feb 1, 2018

I'm having this issue too but deepmerge is being used by a transitive dependency so I can't change the import/require of the function. Any ideas?

@TehShrike TehShrike changed the title Importing deepemerge in a typescript app either breaks the app or the jest tests Having trouble importing deepmerge with TypeScript Feb 1, 2018
@TehShrike
Copy link
Owner

I'm at a disadvantage here because I'm not familiar with TypeScript's module resolution. I'm also not terribly motivated to spend a day learning about it to fix this issue :-x

deepmerge offers a good import experience for both CommonJS consumers (setting module.exports to a single function) and ES Module consumers (exporting the function as the default export).

I don't see any reason to change that in order to facilitate TypeScript. Besides, as more modules start shipping ES Modules, this problem will keep happening to TypeScript users.

That said, it would be nice to know how to fix it.

Are the typings on DefinitelyTyped correct? Could they be changed to fix the issue?

Should an issue be opened on TypeScript? A quick search turns up at least a couple issues related to CommonJS/ESM interop ({#3337](microsoft/TypeScript#3337), #2719), there may be some clues in there.

@pacey, does the library that is consuming deepmerge use the require or import syntax? Does the issue only happen when require is used? How does TypeScript decide whether to use main versus module?

If there is some way to work around this TypeScript issue by fiddling with Webpack/Babel config, it would be good to get it on record here.

@TehShrike TehShrike reopened this Feb 1, 2018
@TehShrike
Copy link
Owner

It may also just be an issue with Webpack. webpack/webpack#5756

@pacey
Copy link

pacey commented Feb 1, 2018

Yeah it's quite possible to just be a Webpack issue @TehShrike. I'll try and get webpack/webpack#5756 resurrected!

@TehShrike
Copy link
Owner

@pacey can you make a bare-bones reproduction in a public repository that is just a single-file app that imports deepmerge and gets bundled with webpack?

@perry-mitchell
Copy link

I'm also getting this issue, but not with typescript: #97

@TehShrike TehShrike changed the title Having trouble importing deepmerge with TypeScript Having trouble importing deepmerge with TypeScript/Webpack Feb 26, 2018
@TehShrike
Copy link
Owner

I opened an issue with Webpack: webpack/webpack#6584

@jonmanzo
Copy link

jonmanzo commented Mar 1, 2018

FWIW I was able to work around setting a resolve alias in the webpack config

alias: {
      deepmerge$: path.resolve(__dirname, 'node_modules/deepmerge/dist/umd.js'),
    }

@TehShrike
Copy link
Owner

Cool - that's probably worth putting into the readme until the Webpack issue is fixed.

@tomwayson
Copy link

tomwayson commented Mar 29, 2018

I was also having this problem w/ Jest (also using TypeScript, but using Rollup to bundle my library), and I came up w/ a different solution that might work for other Jest users until jestjs/jest#2704 is merged/released. I created a manual mock that require()s deepmerge and then exports it out:

// __mocks__/deepmerge.ts 
// (where __mocks__ is a sibling to node_modules)

import deepmerge = require('deepmerge')
export default function merge(...args) {
  return deepmerge(...args)
}

Then I just use import merge from 'deepmerge' in my source code. When running tests, Jest resolves that to the above mock, but during builds, Rollup resolves that to the actual ESM build of deepmerge.

FWIW, @TehShrike I think you're doing everything right on the package.main/package.module front. I think my problems would be solved if Jest was able to respect the module field. Also, I'm not using the DefinitelyTyped typings, so ¯_(ツ)_/¯

@zackypick
Copy link

zackypick commented Apr 20, 2018

if you're just extending simple objects you can quickly roll your own solution, e.g:

export const deepObjectExtend = function (target: any, source: any) {

    for (let prop in source) {
        if (prop in target) {
            deepObjectExtend(target[prop], source[prop]);
        } else {
            target[prop] = source[prop];
        }
    }

    return target;
};

@sasaxing
Copy link

'is-mergeable-object' should be a dependency not dev-dep, shouldn't it?

@TehShrike
Copy link
Owner

The output is bundled with rollup before deploying, so is-mergeable-object's code is included in the deployed version. You can see isMergeableObject inlined in the version deployed to npm on unpkg.

@mesqueeb
Copy link

mesqueeb commented Jun 28, 2018

I have a similar problem where it's looking in all places, just not the node_modules:
with

import merge from 'deepmerge'

it gives

ERROR in /Users/-----/.ghq/github.com/mesqueeb/VuexEasyFirestore/src/utils/setDefaultValues.js
Module not found: Error: Can't resolve 'deepmerge' in '/Users/-----/.ghq/github.com/mesqueeb/VuexEasyFirestore/src/utils'
 @ /Users/-----/.ghq/github.com/mesqueeb/VuexEasyFirestore/src/utils/setDefaultValues.js 1:0-29 11:9-14
 @ /Users/-----/.ghq/github.com/mesqueeb/VuexEasyFirestore/src/module/actions.js
 @ /Users/-----/.ghq/github.com/mesqueeb/VuexEasyFirestore/src/module/index.js
 @ /Users/-----/.ghq/github.com/mesqueeb/VuexEasyFirestore/src/index.js
 @ ./src/store/store.js
 @ ./src/store/index.js

Or should I open a new issue?

I too am able to fix the issue by writing

import merge from '../../node_modules/deepmerge/dist/es.js'

instead. But I don't think this is ideal. I'm not sure why this is happening only to deepmerge, not to other npm packages...

@TehShrike
Copy link
Owner

huh, that's super-weird. That does seem like a separate issue, since it just has to do with resolving the package, instead of returning the CJS versus ESM version of the package.

@mesqueeb
Copy link

@TehShrike
Yeah, I'm not sure what the problem is. Maybe because I'm linking with npm link to this seperate package I'm working on where I use the deepmerge. However, other npm modules work fine there...

I also tried editing the main prop in the package.json of deepmerge to the es version. Didn't work.

Once I am done developing, & rollup my package, i'll let you know if anything has changed.

@mesqueeb
Copy link

mesqueeb commented Jun 30, 2018

@TehShrike
I was able to find exactly what the problem was.

I was working on an npm package which had recently added deepmerge as a dependency.
However through npm link I was testing my npm package inside another package which I was doing a webpack dev hot reload on.

Then it hit me. if I try to npm i deepmerge in this package where I have the webpack running it might work!
And yes it did!

To all people trying to test deepmerge inside another package which is being linked to yet another using webpack hot reload, don't forget to npm i deepmerge in the package having the webpack hot reload enabled.

@davidanaya
Copy link

davidanaya commented Aug 4, 2018

I'm having the same problem as @GioSensation where import merge from 'deepmerge' is working fine in an Angular project but fails when running tests with Jest.

I added

  moduleNameMapper: {
    deepmerge: '<rootDir>/node_modules/deepmerge/dist/es.js',
  },

to my jest.config.js which should be importing then the module from that file instead, and now I get a different error

export default deepmerge_1;
    ^^^^^^

    SyntaxError: Unexpected token export

    > 1 | import merge from 'deepmerge';

So now it seems to be importing the proper file, but still is throwing an error.

@msssk
Copy link
Contributor

msssk commented Sep 28, 2018

The DefinitelyTyped typedefs are bad. I uninstalled them and added the following to my project and it works:

declare module 'deepmerge' {
	function deepmerge<T>(x: Partial<T>, y: Partial<T>, options?: deepmerge.Options): T;
	function deepmerge<T1, T2>(x: Partial<T1>, y: Partial<T2>, options?: deepmerge.Options): T1 & T2;

	namespace deepmerge {
		export interface Options {
			arrayMerge?(target: any[], source: any[], options?: Options): any[];
			clone?: boolean;
			isMergeableObject?(value: object): boolean;
		}

		export function all<T>(objects: Partial<T>[], options?: Options): T;
	}

	export default deepmerge;
}

If you (deepmerge maintainers) are interested in fixing this within your project, you can include the above in deepmerge/index.d.ts. If you would rather offload the maintenance of TS definitions then perhaps I'll submit a PR to DefinitelyTyped (I'm not totally confident in my changes, but they at least make deepmerge basics work for me):

import deepmerge from 'deepmerge';
const opts: deepmerge.Options;
deepmerge(x, y);
deepmerge.all(x, y);

@TehShrike
Copy link
Owner

I'm now open to maintaining a type definition in this project, as long as there is a test that can be added to the npm run scripts, similar to the DefinitelyTyped tests.

A PR to this repo would be appreciated!

@TehShrike TehShrike changed the title Having trouble importing deepmerge with TypeScript/Webpack Having trouble importing deepmerge with Webpack Oct 5, 2018
@sveyret
Copy link

sveyret commented Oct 24, 2018

In the last delivery (2.2.1) a correction was made on the index.d.ts which is making the Typescript import impossible again… You should keep the export default in order to be able to do

import merge from 'deepmerge'

Without this, Typescript users can only do

import * as merge from 'deepmerge'

@msssk
Copy link
Contributor

msssk commented Oct 24, 2018

@sveyret
#121 (comment)

A full-TS option (with better typing as well): dojo/framework/util

@TehShrike
Copy link
Owner

See #124

@sveyret
Copy link

sveyret commented Oct 24, 2018

OK, so this makes sense. But how should we configure Webpack, now, in order not to have the is not a function error? I thought the solution was to take the ES module…

@TehShrike
Copy link
Owner

There's a workaround in the readme that has worked for some people

@sveyret
Copy link

sveyret commented Oct 24, 2018

OK, will try. Thank you.

@sveyret
Copy link

sveyret commented Oct 25, 2018

Defining alias in Webpack is working, but just for information, modules may not be in current project directory (case of workspaces for example). So it would be better not to define absolute path in Webpack configuration. This is how I did in my project:

alias: {
  deepmerge$: 'deepmerge/dist/umd.js',
},

@ciekawy
Copy link

ciekawy commented Oct 28, 2018

I just added deepmerge to my Ionic/Angular TypeScript project and hit the

Error:(25, 8) TS1192: Module '"node_modules/deepmerge/index"' has no default export.

The project have lot of external dependencies and it would be great to have this fixed regardless possible specific webpack (or other tools) limitations.

the workaround for me now is to add in tsconfig.json:

"allowSyntheticDefaultImports": true

also to avoid IDE errors I had to add the above also in tsconfig-ng.json which is being used by unit test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests