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

Dev server breaks library entry type #4469

Closed
DanielStout5 opened this issue May 31, 2022 · 18 comments
Closed

Dev server breaks library entry type #4469

DanielStout5 opened this issue May 31, 2022 · 18 comments

Comments

@DanielStout5
Copy link

DanielStout5 commented May 31, 2022

Bug report

We have an app with two entries - the main one for the app, and a second entry for a library. Both should be served up by the dev server, but the hot reloading breaks the library one.

Actual Behavior

Bottom of app entry file:

/******/ 	__webpack_require__.O(undefined, ["chunk-vendors"], function() { return __webpack_require__("./node_modules/whatwg-fetch/fetch.js"); })
/******/ 	__webpack_require__.O(undefined, ["chunk-vendors"], function() { return __webpack_require__("./node_modules/webpack-dev-server/client/index.js?protocol=wss&hostname=192.168.0.109&port=8081&pathname=%2Fws&logging=none&reconnect=10"); })
/******/ 	__webpack_require__.O(undefined, ["chunk-vendors"], function() { return __webpack_require__("./node_modules/webpack/hot/dev-server.js"); })
/******/ 	var __webpack_exports__ = __webpack_require__.O(undefined, ["chunk-vendors"], function() { return __webpack_require__("./src/main.js"); })
/******/ 	__webpack_exports__ = __webpack_require__.O(__webpack_exports__);

Bottom of library entry file:

/******/ 	__webpack_require__.O(undefined, ["chunk-vendors"], function() { return __webpack_require__("./node_modules/whatwg-fetch/fetch.js"); })
/******/ 	__webpack_require__.O(undefined, ["chunk-vendors"], function() { return __webpack_require__("./node_modules/webpack-dev-server/client/index.js?protocol=wss&hostname=192.168.0.109&port=8081&pathname=%2Fws&logging=none&reconnect=10"); })
/******/ 	__webpack_require__.O(undefined, ["chunk-vendors"], function() { return __webpack_require__("./node_modules/webpack/hot/dev-server.js"); })
/******/ 	var __webpack_exports__ = __webpack_require__.O(undefined, ["chunk-vendors"], function() { return __webpack_require__("./src/library.js"); })
/******/ 	__webpack_exports__ = __webpack_require__.O(__webpack_exports__);
/******/ 	MyLibrary = __webpack_exports__;

The value for MyLibrary ends up being undefined.

Expected Behavior

MyLibrary should be the exported module, not undefined.

It does work in release mode, but not in dev mode when webpack-dev-server is in use.

How Do We Reproduce?

Please paste the results of npx webpack-cli info here, and mention other relevant information

System:
OS: Windows 10 10.0.19044
CPU: (16) x64 11th Gen Intel(R) Core(TM) i7-11700F @ 2.50GHz
Memory: 9.00 GB / 31.73 GB
Binaries:
Node: 14.18.1 - C:\Program Files\nodejs\node.EXE
npm: 6.14.15 - C:\Program Files\nodejs\npm.CMD
Browsers:
Edge: Spartan (44.19041.1266.0), Chromium (101.0.1210.53)
Internet Explorer: 11.0.19041.1566
Packages:
webpack-bundle-analyzer: 4.5.0 => 4.5.0
webpack-dev-server: 4.9.1 => 4.9.1

I've looked at:

I don't really care about having hot reloading for the library. If I could somehow disable that while keeping it for the main app, that would be acceptable.

@alexander-akait
Copy link
Member

How Do We Reproduce?

Please provide more infromation, I think you have a problem in configuration

@DanielStout5
Copy link
Author

DanielStout5 commented May 31, 2022

Here's a sample project demonstrating the issue: https://github.com/DanielStout5/WebpackLibraryHMRIssue

It looks like it might be caused by the combination of the dev server and optimization.splitChunks. The project in question above is actually a Vue one and the Vue-CLI adds a default splitChunks config that specifies a chunk-vendors cache group. Before I added the optimization config, the library export actually did function correctly.

@alexander-akait
Copy link
Member

Please try to set https://webpack.js.org/configuration/optimization/#optimizationruntimechunk, true should be enough

@DanielStout5
Copy link
Author

That configuration is set by Vue-CLI.

My workaround was to delete the optimization section entirely when running in dev mode:

    if (process.env.NODE_ENV == "development") {
      delete config.optimization;
    }

@alexander-akait
Copy link
Member

I think something wrong on vue-cli side in configuration or default configuration is not for you, you have library and application compilation using entries, it is exotic usage, most of developers move library compilation to own compiler (look at multi compiler mode for webpack), also it will allow to reduce time of build and cache

@DanielStout5
Copy link
Author

Yeah I figured it was a bit of an unusual configuration/setup.

Is webpack-dev-server not expected to work with optimization turned on? Because disabling the optimization does fix the issue that seems to be caused by webpack-dev-server.

Failing that, is there a way to exclude webpack-dev-server from affecting a certain entry?

@alexander-akait
Copy link
Member

@DanielStout5 In theory it shoudl work, but if you have non official plugins it can be a problem. You can disable runtime chunks using client: false and add client chunk for required entries (the same approach used for electron applications)

@DanielStout5
Copy link
Author

DanielStout5 commented Jun 3, 2022

Hmm.. Specifying client: false (and hot: false) doesn't seem to prevent all the dev-server additions.

E.g.:

devServer: {
  client: false,
  hot: false
},

That does remove the hot/dev-server line but the webpack-dev-server one with the websocket url is still there:

/******/ 	__webpack_require__("./node_modules/whatwg-fetch/fetch.js");
/******/ 	__webpack_require__("./node_modules/webpack-dev-server/client/index.js?protocol=wss&hostname=192.168.0.109&port=8081&pathname=%2Fws&logging=none&reconnect=10");

@alexander-akait
Copy link
Member

Set hot: false too

@DanielStout5
Copy link
Author

No dice unfortunately. With both client and hot set to false, it still adds the __webpack_require__("./node_modules/webpack-dev-server... line

@alexander-akait
Copy link
Member

alexander-akait commented Jun 4, 2022

@DanielStout5 webpack-dev-server injects entries here https://github.com/webpack/webpack-dev-server/blob/master/lib/Server.js#L612 (and below) (there are three entries, two of them are always inject based on options), as you can see no magic here, try to disable these options

@DanielStout5
Copy link
Author

DanielStout5 commented Jun 14, 2022

@alexander-akait thanks for that link, that was helpful.

I found a very hacky workaround that seems to work. It's a little more complicated because this project is using the Vue CLI and not just plain webpack by itself.

Set webSocketServer to false to prevent adding the additional entries in initialize:

  devServer: {
    webSocketServer: false,

I used the setupMiddlewares callback to restore HMR (normally added in initialize) and set the webSocketServer option back to its default value so createServer will get called later:

    setupMiddlewares(middlewares, devServer) {
      let hmr = new devServer.compiler.webpack.HotModuleReplacementPlugin();
      hmr.apply(devServer.compiler);

      devServer.options.webSocketServer = {
        type: "ws",
        options: { path: "/ws" },
      };
      return middlewares;
    },

And to add the WDS entries only to the app and not my library entrypoint I hardcoded the paths:

    if (process.env.NODE_ENV == "development") {
      appEntries.unshift(
        "./node_modules/webpack-dev-server/client/index.js?protocol=wss&hostname=0.0.0.0&port=8081&pathname=%2Fws&logging=none&reconnect=10"
      );
      appEntries.unshift("./node_modules/webpack/hot/dev-server.js");
    }

Thanks again for your help!

@alexander-akait
Copy link
Member

Yes, you can do it, we added setupMiddlewares for such edge cases, I don't think we will break this API in future, it is very simple and flexibility

@abhchand
Copy link

I had this exact problem. Upgrading to the following latest versions solved it for me

I'm wondering if one other person can replicate this, and then maybe this issue can be closed.

"webpack-dev-server": "^4.11.1",
"webpack": "^5.74.0",
"webpack-cli": "4.10.0"

The relevant portion of my webpack.config.js looks like below. (FYI I'm using webpack with Rails, so the convention is to read from app/frontend/* and output to public/packs/*)

const ASSETS_DIR = path.resolve(__dirname, 'app/frontend');
const PACKS_DIR = path.resolve(__dirname, 'public/packs');
const PUBLIC_DIR = path.resolve(__dirname, 'public');
const PUBLIC_PATH = '/packs/';

module.exports = {
  output: {
    filename: '[name]-[fullhash].js',
    chunkFilename: '[name]-[chunkhash].chunk.js',
    path: PACKS_DIR,
    publicPath: PUBLIC_PATH,
    pathinfo: true,
    library: {
      // Expose each "pack" below as `MyLibrary.<pack>`
      // The value will be the `default` export from each file listed as the
      // `entry...import` below.
      name: ['MyLibrary', '[name]'],
      type: 'var',
      export: 'default'
    }
  },
  devtool: 'cheap-module-source-map',
  devServer: {
    client: {
      logging: 'none'
    },
    compress: true,
    host: 'localhost',
    port: 3035,
    https: false,
    hot: true,
    historyApiFallback: {
      disableDotRule: true
    },
    headers: {
      'Access-Control-Allow-Origin': '*'
    },
    static: {
      directory: PUBLIC_DIR,
      watch: {
        ignored: '/node_modules/'
      }
    }
  },
  entry: {
    admin: {
      import: `${ASSETS_DIR}/javascript/packs/admin.js`,
      dependOn: 'common'
    },
    auth: {
      import: `${ASSETS_DIR}/javascript/packs/auth.js`,
      dependOn: 'common'
    },
    common: `${ASSETS_DIR}/javascript/packs/common.js`
  }
}

@alexander-akait
Copy link
Member

Great! I want to close the issue, and marked as solved based on answers above, feel free to feedback

abhchand added a commit to abhchand/girder that referenced this issue Oct 28, 2022
This includes
  * `webpack`
  * `webpack-dev-server`
  * `webpack-cli`

The current versions have a bug that don't correctly expose a library, if one
is set: webpack/webpack-dev-server#4469.

The issue does not mention an explicit fix version, but upgrading to the latest
webpack dev server seems to have fixed it.

The upgrade also required updating several options that were newly introduced
or deprecated from previous versions.
@kamranayub
Copy link

kamranayub commented Nov 20, 2023

@DanielStout5 I have a similar use case to this, where we are running Docusaurus (webpack + WDS) for our library. I am compiling and including our library as a separate entrypoint (UMD) so that we are effectively building BOTH the docs + library. The reason we'd want to do this is because for embedding live playgrounds where we want our local library code to be reflected (not our npm package). This makes local DX much nicer so when updating docs/compiled examples, it's all synced.

I am ALMOST there, where I can fetch and get the contents of our library.js file when running locally, but WDS is injecting its HMR + dev-server stuff which breaks the UMD module.

I tried your solution but I'm confused on the last bit because it's missing some context.

Where is appEntries in your code? Where would I do that similar logic?

For Docusaurus, we can do something like this:

configureWebpack(config, isServer, util) {

  return {
   devServer: {
     // your middleware code
   }
  }
}

Multi compiler?

most of developers move library compilation to own compiler (look at multi compiler mode for webpack), also it will allow to reduce time of build and cache

I did try this as well by patching multi-config support into my local Docusaurus packages. This still resulted in WDS+HMR code being injected into the bundle because the Docusaurus config comes first and uses that devServer config.

I felt like I was closer, the bundle was cleaner, but still ran into the same problem with the UMD module just not being executed on load.

@DanielStout5
Copy link
Author

DanielStout5 commented Nov 21, 2023

@kamranayub appEntries is just a list of entrypoints I defined:

const appEntries = ["./src/main.js"];

Then I used them like so:

config.entry = () => {
      return {
        app: appEntries,
        tenant: { // alternate non-app config here },
      };
    };

My particular use case was in the context of a Vue CLI application, which does a bunch of webpack config by default that I needed to only apply conditionally which would probably be unnecessary for other use cases

@thesuperzapper
Copy link

In my case, I was able to fix my issue by removing the optimization.splitChunks config.

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

5 participants