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

webpack plugin architecture does not guarantee a plugin will be able to require components from the same version of webpack #12606

Closed
boutell opened this issue Feb 5, 2021 · 9 comments

Comments

@boutell
Copy link

boutell commented Feb 5, 2021

What is the current behavior?

If a project and one of its npm dependencies, let's call it nifty-cms, each depend on two different versions of webpack, i.e. 5 (project level) and 4 (nifty-cms), then npm and yarn will both do the reasonable thing and install webpack twice, with node_modules/nifty-cms/node_nodules containing webpack 4 and node_modules/webpack containing webpack 5. So far so good.

However, if nifty-cms also depends on a plugin — let's say it's vue-loader — and the project does not, then vue-loader will be installed as node_modules/vue-loader. Again, so far so good. Hoisting is normal.

But here's where we get in trouble: unfortunately, when webpack 4 in nifty-cms loads vue-loader, vue-loader then tries to require various files back from webpack itself, for instance:

const BasicEffectRulePlugin = require('webpack/lib/rules/BasicEffectRulePlugin')

At this point, npm (or yarn) sees that vue-loader was loaded from node_modules/vue-loader and loads node_modules/webpack.

But, this is not the version of webpack that instantiated the loader. So the user gets the following error indicating the discrepancy:

The 'compilation' argument must be an instance of Compilation

After puzzling over this for a while I can see two possible resolutions:

  1. All webpack plugin releases could have a peerDependency specifying only one major webpack version, even if they are actually compatible with more than one with no code changes. (vue-loader is compatible with webpack 3, 4, and 5 all at the same time.) This might help, depending on whether npm/yarn actually pay attention to peer dependencies with regard to hosting. And it doesn't require an API change in webpack. But, it requires a lot of redundant publication of modules. And the need for it would be easy to miss.

  2. webpack could provide a webpackRequire function on the compiler object passed to plugins which invokes require from webpack. I think this is the better solution. Unfortunately for this solution to really solve the problem it would need to be universally used. It would be difficult to stomp out the practice of requiring webpack/something when it works any time a project has only one version of webpack in it.

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

Please see the scenario above.

What is the expected behavior?

Projects and their dependent modules should be able to depend on and use different major versions of webpack independently without encountering the following error or its underlying cause:

The 'compilation' argument must be an instance of Compilation

Specifically, webpack plugins should be able to reliably require additional resources from the same webpack version that instantiated them.

Workaround:

A module in the position of nifty-cms can set webpack as a peer dependency instead, and support more than one version of webpack. However work must still immediately be done every time a new version of webpack comes out, and this could become untenable if other webpack plugin dependencies of nifty-cms only work with a single version of webpack in a given release. A further workaround, then, is for nifty-cms to publish nifty-cms-webpack-4-something-loader, nifty-cms-webpack-5-something-loader, etc. which each just require and export a specific version of something-loader. That's a lot of npm packages.

Other relevant information:
webpack version: 4 and 5
Node.js version: any
Operating System: N/A

This issue has been seen in the wild in various projects.

@sokra
Copy link
Member

sokra commented Feb 5, 2021

Yes I'm aware of this problem. Since webpack 5 the Compiler object has a webpack property, which contains the webpack exports of the current version running. So plugins no longer need to require("webpack") or have a webpack dependency at all (a peerDependency might be still a good idea to communicate which webpack version is supported).

Webpack 4 doesn't support it, but you can do const webpack = compiler.webpack || require("webpack") for that case.

class MyPlugin {
  apply(compiler) {
    const webpack = compiler.webpack || require("webpack");
    compiler.hooks.compilation.tap("MyPlugin", compilation => {
      // The following like will fail with a wrong webpack version.
      webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(compilation);
    });
  }
}

But accessing private modules like require('webpack/lib/rules/BasicEffectRulePlugin') is not possible this way, but accessing private modules isn't supported anyway.

@boutell
Copy link
Author

boutell commented Feb 5, 2021 via email

@sokra
Copy link
Member

sokra commented Feb 5, 2021

Actually it should not use that at all. Afaik it tries to copy the rules for CSS, JS, etc for their blocks, but webpack has a better mechnism to run a file as if it is a .css, .js, .ts file: !=!

Example: for a my-file.vue file with

<style>
...
</style>

<script>
...
</script>

the vue-loader can generate code like that:

import "./my-file.vue.css!=!vue-loader/get-block?range=1-3!./my-file.vue";
import "./my-file.vue.js!=!vue-loader/get-block?range=4-6!./my-file.vue";

Where vue-loader/get-block is a loader that extract the content of a block from a range.

The inline syntax ./my-file.vue.css!=! will treat the result of vue-loader/get-block?range=1-3!./my-file.vue as if it was written in ./my-file.vue.css and apply the .css rules to it.

@boutell
Copy link
Author

boutell commented Feb 5, 2021 via email

@alexander-akait
Copy link
Member

So I guess the next question is how common it is for loaders and plugins in heavy use to follow these antipatterns.

When you need it 😄 Why do you think it is antipatterns?

@boutell
Copy link
Author

boutell commented Feb 7, 2021 via email

@boutell
Copy link
Author

boutell commented Feb 7, 2021 via email

@alexander-akait
Copy link
Member

alexander-akait commented Feb 8, 2021

@boutell You need open issues in them repos, we can't fix it here, our recommend solution - using compiler.webpack (compiler.webpack can be used in loaders and plugins without problems), feel free to feedback.

New loader API decried above.

@boutell
Copy link
Author

boutell commented Feb 8, 2021 via email

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

No branches or pull requests

4 participants