I'm working in a Elmish application. I put in place SSR (server-side rendering) for the index page only as I need some dynamically generated initial state at the start of the application. Since the Hot module replacement is not working and it is throwing the following warning:
[HMR] Update failed: SyntaxError: Unexpected token < in JSON at position 0
at JSON.parse (<anonymous>)
at XMLHttpRequest.request.onreadystatechange
I know the config for webpack-devserver is wrong as the onreadystatechange
is retrieving the index page in html rather that the new app file (?) in json.
But reading the documentation didn't help me much as I'm not sure how to prevent request like /en-EN/4b1807ffe818fe814be7.hot-update.json
to be proxied to the back-end.
How can I get HRM to work again?
my webpack.config looks like this:
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;
}
my server relevant part looks like this:
// 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?
]
well, writing the question I thought of something, I was tired lastnight I thing.
So I read the documentation again and found a bypass
option which worked.
I change the proxy config like this:
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 ...
}