vue-loader
vue-loader copied to clipboard
Incorrect HMR behavior with webpack 5 and enabled source maps
Version
16.1.2
Reproduction link
https://github.com/yura3d/vue3-webpack5
Steps to reproduce
- Clone GitHub repo.
- Run
yarn
andyarn dev
. - Browse
localhost:8080
, type some symbols to input field and/or press counter button to change component's state. - Open
src/App.vue
and change something in<template>
section.
What is expected?
HMR applies, input field and counter still contain their values as state shouldn't be changed according to docs: https://vue-loader.vuejs.org/guide/hot-reload.html#state-preservation-rules
What is actually happening?
HMR applies.
State is lost when bundling with webpack 5 if devtool
is set to something with source maps (source-map
, eval-source-map
, etc).
State is preserved when devtool
doesn't set source maps (false
, eval
), or when bundling with webpack 4 (even with source maps) and almost the same webpack.config.js
.
I see this console output on HMR with webpack 5 and source maps:
log.js:24 [HMR] Updated modules:
log.js:16 [HMR] - ./src/App.vue?vue&type=script&lang=js
log.js:24 [HMR] - ./src/App.vue?vue&type=script&lang=js
log.js:24 [HMR] - ./src/App.vue
log.js:16 [HMR] - ./src/App.vue?vue&type=template&id=7ba5bd90&scoped=true
log.js:24 [HMR] - ./src/App.vue?vue&type=template&id=7ba5bd90&scoped=true
log.js:16 [HMR] - ./src/App.vue?vue&type=style&index=0&id=7ba5bd90&lang=scss&scoped=true
log.js:24 [HMR] App is up to date.
hotModuleReplacement.js:215 [HMR] Reload all css
And this on HMR without source maps:
log.js:24 [HMR] Updated modules:
log.js:16 [HMR] - ./src/App.vue?vue&type=template&id=7ba5bd90&scoped=true
log.js:24 [HMR] - ./src/App.vue?vue&type=template&id=7ba5bd90&scoped=true
log.js:16 [HMR] - ./src/App.vue?vue&type=style&index=0&id=7ba5bd90&lang=scss&scoped=true
log.js:24 [HMR] App is up to date.
hotModuleReplacement.js:215 [HMR] Reload all css
It seems component's JS recompiles even when there are no changes in <script>
section if source maps are enabled.
Same issue is happening for my sample project too
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
mode: 'development',
// devtool: 'inline-source-map',
entry: './src/index.ts',
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
],
},
plugins: [
new VueLoaderPlugin()
],
resolve: {
extensions: ['.tsx', '.ts', '.js', '.vue'],
alias: {
vue: 'vue/dist/vue.js'
},
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
devServer: {
contentBase: './dist',
hot: true
},
};
If I uncomment the devtool part, and I change in template / style part the whole component is replaced thus losing the component state.
This behavior reproduces even with projects created by official Vue CLI 5 (@vue/cli@next
, currently in beta), which uses webpack 5 under the hood.
This also happens with vue-loader 15.9.6 on webpack 5. My reproducer is this repository, which uses Webpack Encore as wrapper in a Symfony project.
Same issue here. I can reproduce this:
State is preserved when devtool doesn't set source maps (false, eval), or when bundling with webpack 4 (even with source maps) and almost the same webpack.config.js.
Is there any workaround except disabling devtool options so far?
@Imabigcookie, you don't need to completely disable devtools
, but you do need to set it to source-map
and make sure the target is set to web
.
@bayssmekanique, i've already tried and it doesn't help
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = (env = {}) => ({
mode: 'development',
entry: "./src/index.js",
devtool: 'source-map',
target: 'web',
output: {
publicPath: 'auto',
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
},
],
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './public/index.html'),
})
],
devServer: {
static: {
directory: path.join(__dirname, 'public'),
},
port: 3000,
hot: true,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
},
},
});
{
"name": "core",
"version": "1.0.0",
"description": "",
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production"
},
"author": "",
"license": "ISC",
"devDependencies": {
"html-webpack-plugin": "^5.5.0",
"vue-loader": "^15.9.8",
"vue-template-compiler": "^2.6.14",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.6.0"
},
"dependencies": {
"vue": "^2.6.14",
"vue-router": "^3.5.3",
"vuex": "^3.6.2"
}
}
The bug is still reproducible in 17.0.0, even on fresh setup using the newest @vue/cli
5
any solution to this? I'm having the same issue. Hot reload only works with devTool=false.
using this combination:
"typescript": "^4.7.4",
"vue-loader": "^17.0.0",
"vue-style-loader": "^4.1.3",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.3"
15 months and no fix :(
This nasty bug forces me to revert back to Vue 2 and Webpack 4.
This nasty bug forces me to revert back to Vue 2 and Webpack 4.
If you have the opportunity to try ViteJS, you might never go back to Webpack.
This nasty bug forces me to revert back to Vue 2 and Webpack 4.
If you have the opportunity to try ViteJS, you might never go back to Webpack.
Vite has issues, too - it does not allow me to put breakpoints inside my .vue
files. I have to add/remove debugger
statements. HMR in Vite is not perfect, too - sometimes it just does not notice that I've changed something in my code and I have to reload the page.
@sodatea I debug this today, it caused by select script block return sourcemap at https://github.com/vuejs/vue-loader/blob/next/src/select.ts#L33. The sourcemap which include all content of a vue file, it is different after code change, it will generate different hash at webpack5(used webpack-source at https://github.com/webpack/webpack-sources/blob/main/lib/SourceMapSource.js#L233), so i change template block will caused script block change. The webpack4 worked fine, becasue it is not calculate hash for sourcemap at [email protected], see https://github.com/webpack/webpack-sources/blob/v1.4.3/lib/SourceMapSource.js#L50.
@underfin So, do you have any ideas how to fix this bug?
@tmcdos Maybe you can use patch-package
to remove https://github.com/webpack/webpack-sources/blob/main/lib/SourceMapSource.js#L233 this line, but might meet sourcemap line error...
@tmcdos Maybe you can use
patch-package
to remove https://github.com/webpack/webpack-sources/blob/main/lib/SourceMapSource.js#L233 this line, but might meet sourcemap line error...
没有别的办法了吗,看上面只能设置 devtool: false
激活热更
It looks like I found a workaround - if I move https://github.com/webpack/webpack-sources/blob/main/lib/SourceMapSource.js#L233 between https://github.com/webpack/webpack-sources/blob/9f98066311d53a153fdc7c633422a1d086528027/lib/SourceMapSource.js#L235 and https://github.com/webpack/webpack-sources/blob/9f98066311d53a153fdc7c633422a1d086528027/lib/SourceMapSource.js#L236 so it looks like
if (this._hasOriginalSource)
{
hash.update(this._sourceMapAsBuffer); // originally this was outside of the IF block
hash.update(this._originalSourceAsBuffer);
}
then if I only change something in the component template (e.g. a CSS class) - the script section of the component is not reloaded. I do not know if this is a proper fix because I am totally clueless about the inner workings of Webpack and Webpack-Sources package in particular. I use custompatch to patch webpack-sources in my project.