私は Elmish アプリケーションで作業しています。アプリケーションの開始時に動的に生成される初期状態が必要なため、インデックス ページにのみ SSR (サーバー側レンダリング) を配置します。ホットモジュール交換が機能せず、次の警告がスローされているため:
[HMR] Update failed: SyntaxError: Unexpected token < in JSON at position 0
at JSON.parse (<anonymous>)
at XMLHttpRequest.request.onreadystatechange
onreadystatechange
json の新しいアプリ ファイル (?) ではなく、html でインデックス ページを取得しているため、webpack-devserver の構成が間違っていることはわかっています。/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?
]