Dev server breaks library entry type
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?
- Create a project with multiple entries, one of which is a library (var output)
- Run it in dev mode
- Here's an example reproduction: https://github.com/DanielStout5/WebpackLibraryHMRIssue
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:
- https://github.com/webpack/webpack-dev-server/issues/2484 (says to upgrade to webpack-dev-server 4, which I'm above)
- https://github.com/webpack/webpack-dev-server/issues/2692 (about module federation, not exactly what I'm doing - and this one is still open)
- https://github.com/webpack/webpack/issues/12793 (Also says fixed in wds 4)
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.
How Do We Reproduce?
Please provide more infromation, I think you have a problem in configuration
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.
Please try to set https://webpack.js.org/configuration/optimization/#optimizationruntimechunk, true should be enough
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;
}
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
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?
@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)
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");
Set hot: false too
No dice unfortunately. With both client and hot set to false, it still adds the __webpack_require__("./node_modules/webpack-dev-server... line
@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
@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!
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
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`
}
}
Great! I want to close the issue, and marked as solved based on answers above, feel free to feedback
@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.
@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