HMR - Update failed: SyntaxError: Unexpected token < in JSON at position 0

HMR - Update failed: SyntaxError: Unexpected token < in JSON at position 0

我在 Elmish 应用程序中工作。 我只为索引页面设置了 SSR(服务器端呈现),因为我需要在应用程序启动时动态生成一些初始状态。 由于热模块更换不起作用,它会抛出以下警告:

[HMR] Update failed: SyntaxError: Unexpected token < in JSON at position 0
at JSON.parse (<anonymous>)
at XMLHttpRequest.request.onreadystatechange

我知道 webpack-devserver 的配置是错误的,因为 onreadystatechange 正在检索 html 中的索引页面,而不是 json 中的新应用程序文件 (?)。 但是阅读文档对我没有多大帮助,因为我不确定如何防止像 /en-EN/4b1807ffe818fe814be7.hot-update.json 这样的请求被代理到后端。

如何让 HRM 重新工作?

我的 webpack.config 看起来像这样:

var path = require('path');
var webpack = require('webpack');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var MiniCssExtractPlugin = require('mini-css-extract-plugin');

var CONFIG = {
    // The tags to include the generated JS and CSS will be automatically injected in the HTML template
    // See https://github.com/jantimon/html-webpack-plugin
    fsharpEntry: './src/UI/Client/Client.fsproj',
    lessEntry: './src/UI/Client/style.less',
    outputDir: './src/UI/Client/deploy',
    assetsDir: './src/UI/Client/public',
    devServerPort: 8080,
    // When using webpack-dev-server, you may need to redirect some calls
    // to a external API server. See https://webpack.js.org/configuration/dev-server/#devserver-proxy
    devServerProxy: {
        '/': {
            target: 'http://localhost:' + (process.env.SERVER_PROXY_PORT || "8085"),
            changeOrigin: true
        },
        // redirect requests that start with /api/* to the server on port 8085
        '/api/*': {
            target: 'http://localhost:' + (process.env.SERVER_PROXY_PORT || "8085"),
            changeOrigin: true
        },
        // redirect websocket requests that start with /socket/* to the server on the port 8085
        '/socket/*': {
            target: 'http://localhost:' + (process.env.SERVER_PROXY_PORT || "8085"),
            ws: true
        }
    },
    // Use babel-preset-env to generate JS compatible with most-used browsers.
    // More info at https://babeljs.io/docs/en/next/babel-preset-env.html
    babel: {
        presets: [
            ['@babel/preset-env', {
                modules: false,
                // This adds polyfills when needed. Requires core-js dependency.
                // See https://babeljs.io/docs/en/babel-preset-env#usebuiltins
                useBuiltIns: 'usage',
                corejs: 3
            }]
        ],
    }
}

// If we're running the webpack-dev-server, assume we're in development mode
var isProduction = !process.argv.find(v => v.indexOf('webpack-dev-server') !== -1);
console.log('Bundling for ' + (isProduction ? 'production' : 'development') + '...');

module.exports = _ => {

    var commonPlugins = [];

    return {
        // In development, split the JavaScript and CSS files in order to
        // have a faster HMR support. In production bundle styles together
        // with the code because the MiniCssExtractPlugin will extract the
        // CSS in a separate files.
        entry: isProduction ? {
            app: [resolve(CONFIG.fsharpEntry), resolve(CONFIG.lessEntry)]
        } : {
                app: [resolve(CONFIG.fsharpEntry)],
                style: [resolve(CONFIG.lessEntry)]
            },
        // Add a hash to the output file name in production
        // to prevent browser caching if code changes
        output: {
            path: resolve(CONFIG.outputDir),
            filename: isProduction ? '[name].[hash].js' : '[name].js'
        },
        mode: isProduction ? 'production' : 'development',
        devtool: isProduction ? 'source-map' : 'eval-source-map',
        optimization: {
            splitChunks: {
                chunks: 'all'
            },
        },
        // Besides the HtmlPlugin, we use the following plugins:
        // PRODUCTION
        //      - MiniCssExtractPlugin: Extracts CSS from bundle to a different file
        //          To minify CSS, see https://github.com/webpack-contrib/mini-css-extract-plugin#minimizing-for-production
        //      - CopyWebpackPlugin: Copies static assets to output directory
        // DEVELOPMENT
        //      - HotModuleReplacementPlugin: Enables hot reloading when code changes without refreshing
        plugins: isProduction ?
            commonPlugins.concat([
                new MiniCssExtractPlugin({ filename: 'style.[hash].css' }),
                new CopyWebpackPlugin({ patterns : [{ from: resolve(CONFIG.assetsDir) }]}),
            ])
            : commonPlugins.concat([
                new webpack.HotModuleReplacementPlugin(),
            ]),
        resolve: {
            // See https://github.com/fable-compiler/Fable/issues/1490
            symlinks: false
        },
        // Configuration for webpack-dev-server
        devServer: {
            publicPath: '/',
            contentBase: resolve(CONFIG.assetsDir),
            host: '0.0.0.0',
            port: CONFIG.devServerPort,
            proxy: CONFIG.devServerProxy,
            hot: true,
            inline: true,
            historyApiFallback: true
        },
        // - fable-loader: transforms F# into JS
        // - babel-loader: transforms JS to old syntax (compatible with old browsers)
        // - sass-loaders: transforms SASS/SCSS into JS
        // - file-loader: Moves files referenced in the code (fonts, images) into output folder
        module: {
            rules: [
                {
                    test: /\.fs(x|proj)?$/,
                    use: {
                        loader: 'fable-loader',
                        options: {
                            babel: CONFIG.babel
                        }
                    }
                },
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    use: {
                        loader: 'babel-loader',
                        options: CONFIG.babel
                    },
                },
                {
                    test: /\.(le|c)ss$/,
                    use: [
                        isProduction
                            ? MiniCssExtractPlugin.loader
                            : 'style-loader',
                        'css-loader',
                        {
                            loader: 'less-loader',
                            options: { implementation: require('less') }
                        }
                    ]
                },
                {
                    test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?.*)?$/,
                    use: ['file-loader']
                }
            ]
        }
    };
};

function resolve(filePath) {
    console.log("resolve filepath: " + filePath);
    var resolved = path.isAbsolute(filePath) ? filePath : path.join(__dirname, filePath);
    console.log("resolved: " + resolved);
    return resolved;
}

我的服务器相关部分如下所示:

// Fable.Remoting api (as giraffe HttpHandler)
let createApi config httpClient translator=
    Remoting.createApi()
    |> Remoting.withErrorHandler errorHandler
    |> Remoting.withRouteBuilder Route.builder
    |> Remoting.fromValue (api config httpClient translator)
    |> Remoting.buildHttpHandler

let printRequestPath : HttpHandler =
    fun next ctx ->
        printfn "request path: %O" ctx.Request.Path
        next ctx

let webApp config httpClient translator =
    choose [
        GET >=> route "/" >=> Index.indexHandler config
        createApi config httpClient translator
        GET >=> Index.indexHandler config // default every GET route to index so client application handles it. TODO: there is a better way? rewriting on prod? on webpack-devserver historyapifallback?
    ]

好吧,写题我想到了什么,昨晚累死我了嘛。 所以我再次阅读了文档并找到了一个有效的 bypass 选项。

我这样更改代理配置:

devServerProxy: {
    '/': {
            target: 'http://localhost:' + (process.env.SERVER_PROXY_PORT || "8085"),
            changeOrigin: true,
            bypass: function(req, res, proxyOptions) {
                if (req.path.indexOf('.hot-update.js') !== -1) {
                    var lastSlashIndex = req.path.lastIndexOf('/');
                    return req.path.substr(lastSlashIndex);
                }
                return null;
            }
        }
// rest as before ...
}