0

During development of SPA, hmr works great.

However, when published, hmr should not be running. Yet it is, and it produces a stream of 404 errors. Why is this? I don't see what I am doing wrong.

When I package for production, this is the command line (I am running this from the Visual Studio Task Runner):

cmd /c SET NODE_ENV=production&& webpack --config webpack.netcore.config.js

webpack.netcore.config.js

const webpackConfig = require('./webpack.config');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
var originalConfig = webpackConfig({});

module.exports = () => {
  let config = originalConfig;
  // output files without hashes
  config.output.filename = '[name].bundle.js';
  config.plugins.splice(config.plugins.indexOf(HtmlWebpackPlugin));
  config.plugins = [
    // first clean the output directory
    new CleanWebpackPlugin([config.output.path]),
    ...config.plugins
  ];

  return config;
};

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const project = require('./aurelia_project/aurelia.json');
const { AureliaPlugin, ModuleDependenciesPlugin } = require('aurelia-webpack-plugin');
const { optimize: { CommonsChunkPlugin, UglifyJsPlugin }, ProvidePlugin } = require('webpack');
const { TsConfigPathsPlugin, CheckerPlugin } = require('awesome-typescript-loader');

// config helpers:
const ensureArray = (config) => config && (Array.isArray(config) ? config : [config]) || [];
const when = (condition, config, negativeConfig) =>
  condition ? ensureArray(config) : ensureArray(negativeConfig);

// primary config:
const title = 'Aurelia Navigation Skeleton';
const outDir = path.resolve(__dirname, project.platform.output);
const srcDir = path.resolve(__dirname, 'src');
const nodeModulesDir = path.resolve(__dirname, 'node_modules');
const baseUrl = '/';

const cssRules = [
  { loader: 'css-loader' },
];

module.exports = ({production, server, extractCss, coverage} = {}) => ({
  resolve: {
    extensions: ['.ts', '.js'],
    modules: [srcDir, 'node_modules'],
  },
  entry: { 
    app: ['aurelia-bootstrapper'],
    vendor: ['bluebird','aurelia-syncfusion-bridge'],
  },
  output: {
    path: outDir,
    publicPath: baseUrl,
    filename: production ? '[name].[chunkhash].bundle.js' : '[name].[hash].bundle.js',
    sourceMapFilename: production ? '[name].[chunkhash].bundle.map' : '[name].[hash].bundle.map',
    chunkFilename: production ? '[name].[chunkhash].chunk.js' : '[name].[hash].chunk.js'
  },
  devServer: {
    contentBase: outDir,
    // serve index.html for all 404 (required for push-state)
    historyApiFallback: true
  },
  devtool: production ? 'nosources-source-map' : 'cheap-module-eval-source-map',
  module: {
    rules: [
      // CSS required in JS/TS files should use the style-loader that auto-injects it into the website
      // only when the issuer is a .js/.ts file, so the loaders are not applied inside html templates
      {
        test: /\.css$/i,
        issuer: [{ not: [{ test: /\.html$/i }] }],
        use: extractCss ? ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: cssRules
        }) : ['style-loader', ...cssRules],
      },
      {
        test: /\.css$/i,
        issuer: [{ test: /\.html$/i }],
        // CSS required in templates cannot be extracted safely
        // because Aurelia would try to require it again in runtime
        use: cssRules
      },
      { test: /\.html$/i, loader: 'html-loader' },
      { test: /\.ts$/i, loader: 'awesome-typescript-loader', exclude: nodeModulesDir },
      { test: /\.json$/i, loader: 'json-loader' },
      // use Bluebird as the global Promise implementation:
      { test: /[\/\\]node_modules[\/\\]bluebird[\/\\].+\.js$/, loader: 'expose-loader?Promise' },
      // embed small images and fonts as Data Urls and larger ones as files:
      { test: /\.(png|gif|jpg|cur)$/i, loader: 'url-loader', options: { limit: 8192 } },
      { test: /\.woff2(\?v=[0-9]\.[0-9]\.[0-9])?$/i, loader: 'url-loader', options: { limit: 10000, mimetype: 'application/font-woff2' } },
      { test: /\.woff(\?v=[0-9]\.[0-9]\.[0-9])?$/i, loader: 'url-loader', options: { limit: 10000, mimetype: 'application/font-woff' } },
      // load these fonts normally, as files:
      { test: /\.(ttf|eot|svg|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/i, loader: 'file-loader' },
      ...when(coverage, {
        test: /\.[jt]s$/i, loader: 'istanbul-instrumenter-loader',
        include: srcDir, exclude: [/\.{spec,test}\.[jt]s$/i],
        enforce: 'post', options: { esModules: true },
      })
    ]
  },
    plugins: [

    new AureliaPlugin(),
    new ProvidePlugin({
      'Promise': 'bluebird'
    }),
    new ModuleDependenciesPlugin({
      'aurelia-testing': [ './compile-spy', './view-spy' ]
    }),
    new ModuleDependenciesPlugin({
        "aurelia-orm": [
            "./component/association-select",
            "./component/view/bootstrap/association-select.html",
            "./component/view/bootstrap/paged.html",
            "./component/paged"],
        "aurelia-authentication": ["./authFilterValueConverter"]
    }),
    new TsConfigPathsPlugin(),
    new CheckerPlugin(),
    new HtmlWebpackPlugin({
      template: 'index.ejs',
      metadata: {
        // available in index.ejs //
        title, server, baseUrl
      }
    }),
    ...when(extractCss, new ExtractTextPlugin({
      filename: production ? '[contenthash].css' : '[id].css',
      allChunks: true
    })),
    ...when(production, new CommonsChunkPlugin({
      name: ['common']
    })),
    ...when(production, new CopyWebpackPlugin([
      { from: 'static/favicon.ico', to: 'favicon.ico' }
    ])),
    ...when(production, new UglifyJsPlugin({
      sourceMap: true
    }))
  ]
});

The 404 error looks like this: https://trkn.app/__webpack_hmr

Greg Gum
  • 33,478
  • 39
  • 162
  • 233

2 Answers2

3

The only two reasons for HMR running in production are:

  1. If you see it on a server log, it means that a dev version of the app is still open in a browser and hitting the server.

  2. If you see it on the client, it means that the production environment was not actually set when building with webpack.

This is code that I have in webpack.config.js that sets the environment, and then prints the environment to the console that I am sure the correct environment is selected. (Note that this code is specific to my dev set up (VS17 + Taskrunner) so your mileage may vary.

const path = require("path");
const webpack = require("webpack");
const { AureliaPlugin, ModuleDependenciesPlugin, GlobDependenciesPlugin } = require("aurelia-webpack-plugin");
const bundleOutputDir = "./wwwroot/dist";
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = (env, args) => {
    let isDevBuild = true;  //Assume isDevBuild;

    //If being run from NPM, args.mode will be populated
    if (args && args.mode === 'production') {
        isDevBuild = false;
    }

    //Not production mode from NPM, check on Production mode from Task Runner
    if (isDevBuild) {
        //If being run from the Webpack Task Runner in VS.
        const node_env = process.env.NODE_ENV

        if (node_env) {
            if (node_env === 'production') {
                isDevBuild = false;
            }
            else {
            }
        }
    }
    //isDevBuild = true;//Uncomment to test the Prod Build
    console.log('isDevBuild=' + isDevBuild);
    const cssLoader = { loader: isDevBuild ? "css-loader" : "css-loader?minimize" };
    return [{
        target: "web",
        mode: isDevBuild ? "development" : "production",
        entry: { "app": ["es6-promise/auto", "aurelia-bootstrapper"] },
        resolve: {
            extensions: [".ts", ".js"],
            modules: ["ClientApp", "node_modules"]
        },
        output: {
            path: path.resolve(bundleOutputDir),
            publicPath: "/dist/",
            filename: "[name].js",
            chunkFilename: "[name].js"
        },
        module: {
            rules: [
                { test: /\.(woff|woff2)(\?|$)/, loader: "url-loader?limit=1" },
                { test: /\.(png|eot|ttf|svg|gif|cur)(\?|$)/, loader: "url-loader?limit=100000" },
                { test: /\.ts$/i, include: [/ClientApp/, /node_modules/], use: "awesome-typescript-loader" },
                { test: /\.html$/i, use: "html-loader" },
                {   test: /\.css$/, include: [/node_modules/], use: [
                        {
                            loader: MiniCssExtractPlugin.loader
                        },
                        "css-loader"
                    ]
                },
                {
                    test: /\.css$/, exclude: [/node_modules/], use: [
                        {
                            loader: MiniCssExtractPlugin.loader
                        },
                        "css-loader"
                    ]
                },
                { test: /\.scss$/i, issuer: /(\.html|empty-entry\.js)$/i, use: [cssLoader, "sass-loader"] },
                { test: /\.scss$/i, issuer: /\.ts$/i, use: ["style-loader", cssLoader, "sass-loader"] }
            ]
        },
        optimization: {
            splitChunks: {
                cacheGroups: {
                    commons: {
                        test: /[\\/]node_modules[\\/]/,
                        name: "vendor",
                        chunks: "all"
                    }
                }
            }
        },
        devtool: isDevBuild ? "source-map" : false,
        performance: {
            hints: false
        },
        plugins: [
            new webpack.DefinePlugin({ IS_DEV_BUILD: JSON.stringify(isDevBuild) }),
            new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", "window.jQuery": "jquery" }),
            new webpack.ProvidePlugin({
                'Promise': 'bluebird'
            }),

            new AureliaPlugin({ aureliaApp: "boot" }),
            new GlobDependenciesPlugin({ "boot": ["ClientApp/**/*.{ts,html}"] }),
            new ModuleDependenciesPlugin({}),
            //extractCSS,
            new ModuleDependenciesPlugin({
                "aurelia-orm": [
                    "./component/association-select",
                    "./component/view/bootstrap/association-select.html",
                    "./component/view/bootstrap/paged.html",
                    "./component/paged"],
                "aurelia-authentication": ["./authFilterValueConverter"]
            }),

                new MiniCssExtractPlugin({
                    // Options similar to the same options in webpackOptions.output
                    // both options are optional
                    filename: "[name].css",
                    chunkFilename: "[id].css"
            })

        ]
    }];
};

Also note that there are dev/prod features that are set based on the isDevBuild variable. So in addition to setting the environment, you have to set the options accordingly.

Greg Gum
  • 33,478
  • 39
  • 162
  • 233
  • Can you be more specific about "production environment was not actually set when building with webpack". I'm using Webpack v4 and I have set `production` is multiple places: NODE_ENV=production && webpack -env.production, mode: production in package.json and I'm still getting an error in the browser about HMR running in production – SomethingOn Dec 12 '18 at 15:17
  • @SomethingOn, Your settings are correct. Well, at least they appear to be correct. But following logic: If you are getting a 404 error, it means the HMR request is coming from the client. If it is coming from the client, it means that webpack has included the HMR code. If the HMR code was included, it means that the environment was set to dev, not production. So even though it appears you have set it correctly, the end result is still that it is dev, not prod. – Greg Gum Dec 13 '18 at 06:49
  • I updated my answer to include the config file that I use. The other thing is that maybe 'isDevBuild' is not being correctly used in your webpack config file (see example above.) – Greg Gum Dec 13 '18 at 06:58
  • What is ended up being for me was `NODE_ENV=production webpack...` vs `NODE_ENV=production && webpack...` in the `scripts` section of package.json. Apparently it didn't like the && – SomethingOn Dec 13 '18 at 21:48
  • @SomethingOn, Glad that got resolved. Feel free to upvote my answer :) – Greg Gum Dec 14 '18 at 19:30
0

Please have a detailed look at the path which is throwing 404 (page not found). I guess this is often the issue with relative path.

MattOpen
  • 815
  • 11
  • 24