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

Resolve module (mjs) incorrectly when using Module Federation Plugin #16125

Open
tzachbon opened this issue Aug 8, 2022 · 23 comments
Open

Resolve module (mjs) incorrectly when using Module Federation Plugin #16125

tzachbon opened this issue Aug 8, 2022 · 23 comments

Comments

@tzachbon
Copy link

tzachbon commented Aug 8, 2022

Bug report

What is the current behavior?

When using swr/infinite with swr>1.1.2 dependency and sharing it with Module Federation (SharePlugin)

    new webpack.container.ModuleFederationPlugin({
      shared: ['swr'],
    }),

We encountered this error:
with-mf

If the current behavior is a bug, please provide the steps to reproduce.

I created this repo that reproduces the issue: https://github.com/tzachbon/swr-mf-error

All you have to do is to use SWR >= 1.2.0 and add it to the shared array in the Module Federation Plugin.
In the change log here: vercel/swr@1.1.2...1.2.0
I found a few things that might be the reason.

In my code, I try to import swr/infinite, which now has exports field in its package json:

  "exports": "./dist/index.mjs",

In the swr package json, the exports field now has a filename that ends with mjsextension:

    "./infinite": {
      "import": "./infinite/dist/index.mjs",
      "module": "./infinite/dist/index.esm.js",
      "require": "./infinite/dist/index.js",
      "types": "./infinite/dist/infinite/index.d.ts"
    },

When I manually changed the filename to .js it worked.

What is the expected behavior?

To resolve it like .js

Other relevant information:
webpack version: 5.74.0
Node.js version: 16.15.0
Operating System: MacBook Pro (16-inch, 2021)
Additional tools: swr: 1.3.0

@huozhi
Copy link
Contributor

huozhi commented Aug 8, 2022

Add some addition infomration: The esm assets of swr ("import" condition from "exports" field in package.json) is picked up by webpack correctly, but it seems still act like commonjs module with module federation plugin.

If I removed the import export condition then everything works well. But it's still picking up the "module" condition from "exports", which uses exact same esm format like mjs assets does. I think webpack should treat the "module" and "import" condition similarly?

@keropodium
Copy link

You can skip this issue by also sharing any submodule of swr changing this:

new webpack.container.ModuleFederationPlugin({
    shared: ['swr'],
}),  

to this

new webpack.container.ModuleFederationPlugin({
    shared: ['swr/'],
}),  

@huozhi
Copy link
Contributor

huozhi commented Aug 10, 2022

@keropodium Thanks! I checked, it resolved the issue! cc @tzachbon

@tzachbon
Copy link
Author

You can skip this issue by also sharing any submodule of swr changing this:

new webpack.container.ModuleFederationPlugin({
    shared: ['swr'],
}),  

to this

new webpack.container.ModuleFederationPlugin({
    shared: ['swr/'],
}),  

Thanks! It does work but isn't it a workaround for an existing bug with the resolution?

@keropodium
Copy link

keropodium commented Aug 12, 2022

Thanks! It does work but isn't it a workaround for an existing bug with the resolution?

@tzachbon Correct, this issue still needs to be addressed.

@ScriptedAlchemy
Copy link
Member

I think it may be a bug if the default isn’t resolving to something. If you’re doing a “deep” import into the package. Then trailing slash is needed

@ZacButko
Copy link

1 we just found this bug

I think it may be a bug if the default isn’t resolving to something.

I'm not sure from the context whether you mean this is a bug with swr or webpack?

If you’re doing a “deep” import into the package. Then trailing slash is needed

Is the trailing slash a normal convention or is this a workaround?

@alexander-akait
Copy link
Member

Feel free to send a PR

@tzachbon
Copy link
Author

tzachbon commented Sep 23, 2022

I will share my insights after a little more investigating:

  • When I add swr to the shared dependency, it treats it as commonjs only when it resolves from the swr/infinite context. For example, it resolves correctly when I import it in the index file, and the context is src/index.js.
  • The reason adding a slash suffix works is because swr is not shared anymore (only swr/infinite is shared) therefore, it is treated as mjs from any context.

I am not sure where createConsumeSharedModule makes it behave like that (hopefully you (@ScriptedAlchemy) will tell me) or the issue is rooted in Enhance resolver.

@ScriptedAlchemy
Copy link
Member

I think the issue is in that file since webpack resolves correctly. It must be the "result" here which isn't resolved to the right dependency

@tzachbon
Copy link
Author

tzachbon commented Oct 1, 2022

Another small breakthrough for me.
When I added to the webpack .mjs default the following config it works:

{
	test: /\.mjs$/i,
	descriptionData: {
		type: "module"
	},
	...esm
},

The problem is when the consumer extension is .mjs too, it breaks for the following reason -
It calls swr__WEBPACK_IMPORTED_MODULE_0__ and not swr__WEBPACK_IMPORTED_MODULE_0___default.

I'm not sure where and when the export type should be "dynamic" or "default-with-named", but it looks like when the descriptionData.type is module it becomes "dynamic" and uses some kind of esModuleInterop.

@Twipped
Copy link

Twipped commented Nov 21, 2022

Just gonna add a few keywords here for google, because it took me WEEKS to find this issue:

graphql-tag default export import
TypeError: graphql_tag__WEBPACK_IMPORTED_MODULE_0__ is not a function

@Twipped
Copy link

Twipped commented Nov 28, 2022

I've created a really small reproduction case of this. I was able to make it happen even with local dependencies.

https://github.com/ZenTwipped/webpack-default-repro

Just npm install and npm start. The page that opens will display a message indicating if the package loaded correctly.

@Twipped
Copy link

Twipped commented Dec 2, 2022

I've been able to figure out that the issue is in the detection of exportsType of a shared module when passing through from another shared module, it comes back as default-with-named instead of dynamic, which then means that interopDefaultAccessUsed never gets set to true, and compatGetDefaultExport never gets added to the build. What I can't figure out is why it breaks only when coming in through another shared module. If imported as a first-level shared module, this doesn't happen, the file is correctly typed as dynamic.

@webpack-bot
Copy link
Contributor

This issue had no activity for at least three months.

It's subject to automatic issue closing if there is no activity in the next 15 days.

@Twipped
Copy link

Twipped commented Mar 7, 2023

Is there anybody more intimate with the container inner workings that can comment on this? The Send a PR tag isn't helpful when nobody is able to work out what's causing it.

@TheLarkInn
Copy link
Member

@ScriptedAlchemy would be the main implementor or archetect for MF. I'm sure he could provide some guidance.

@hangboss1761
Copy link

I made a cleaner demo to help you reproduce this bug,https://stackblitz.com/edit/webpack-mf-bug-with-mjs?file=README.md

Tese Case

Shared package(demo-package) provite index.js/index.mjs/index.esm.js,the default exported file is index.js.

index.js:

class AClass {
  constructor() {
    this.a = 1
  }

  install() {
    console.log('this.a', this.a)
  }
}

module.exports = AClass

index.mjs/index.esm.js:

export default class AClass {
  constructor() {
    this.a = 1
  }

  install() {
    console.log('this.a', this.a)
  }
}

Case1

In app-a

webpack.config.js

const path = require('path');
const { ModuleFederationPlugin } = require('webpack').container

module.exports = {
  entry: './src/index.js',
  mode: 'development',
  target: 'node',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  resolve: {
    alias: {},
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'webpack-mf-bug-with-mjs',
      library: { type: 'var', name: 'webpack-mf-bug-with-mjs' },
      filename: 'remoteEntry.js',
      shared: ['demo-package'],
    }),
  ],
};

The result of running bundle file is:

packages/app-a test: AClass from test.mjs:  [class AClass]
packages/app-a test: AClass from test.js:  [class AClass]

Case2

In app-b, change config to export a mjs or .esm.js file from demo-package.

webpack.config.js

const path = require('path');
const { ModuleFederationPlugin } = require('webpack').container

module.exports = {
  entry: './src/index.js',
  mode: 'development',
  target: 'node',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  resolve: {
    alias: {
      'demo-package': 'demo-package/index.mjs'
      // NOTE: .esm.js file got the same result
      // 'demo-package': 'demo-package/index.esm.js'
    },
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'webpack-mf-bug-with-mjs',
      library: { type: 'var', name: 'webpack-mf-bug-with-mjs' },
      filename: 'remoteEntry.js',
      shared: ['demo-package'],
    }),
  ],
};

The result of running bundle file is:

packages/app-b test: AClass from test.mjs:  Object [Module] { default: [Getter] }
packages/app-b test: AClass from test.js:  [class AClass]

Got an unexpected result in test.mjs

Case3

In app-c, do not share package, everything is normal.

webpack.config.js

const path = require('path');
const { ModuleFederationPlugin } = require('webpack').container

module.exports = {
  entry: './src/index.js',
  mode: 'development',
  target: 'node',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  resolve: {
    alias: {
      'demo-package': 'demo-package/index.mjs'
      //NOTE: .esm.js file got the same result
      // 'demo-package': 'demo-package/index.esm.js'
    },
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'webpack-mf-bug-with-mjs',
      library: { type: 'var', name: 'webpack-mf-bug-with-mjs' },
      filename: 'remoteEntry.js',
      // NOTE: Don't share the package, you will get the desired result
      // shared: ['demo-package'],
    }),
  ],
};

The result of running bundle file is:

packages/app-c test: AClass from test.mjs:  [class AClass]
packages/app-c test: AClass from test.js:  [class AClass]

@alexander-akait
Copy link
Member

Yeah, looks like a bug, someone wants to send a PR?

@tzachbon
Copy link
Author

Yeah, looks like a bug, someone wants to send a PR?

I don't mind but can somebody guide me on where to focus?

@shafferchance
Copy link

shafferchance commented Apr 5, 2023

I'm certainly no expert in how everything weaves together but if this is a question of resolution and it appears to be triggered by shared according to the bug replication by @hangboss1761 I would say a good starting place is here (The link will open the GitHub dev editor)

Relative path in code: webpack/webpack/lib/sharing/ProvideSharedPlugin.js

I don't have time to take a deep dive at the moment but hopefully, that helps @tzachbon

Later I can take a look, ran into this I believe after adding the exports field to my package, and the ESM was resolved but then it appeared to not see exports I'll blame that piece on my resolution. Upon inspection, the exports object just didn't exist in the generated Module, once again probably my configuration but if that helps there you go.

However, I resolved it by setting it back to my UMD build via an explicit path. The trailing slash did not work with my scoped package.

If the aforementioned behavior is connected then, it looks like the path came from the ConsumeSharedPlugin.

@webpack-bot
Copy link
Contributor

Issue was closed because of inactivity.

If you think this is still a valid issue, please file a new issue with additional information.

@ScriptedAlchemy
Copy link
Member

Bump

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Priority - Medium
Development

No branches or pull requests