1

I have a standard folder structure that is a bit like this:

.
├── dist
|   ├── vendor.js
|   └── app.js              
|   └── index.html   
├── src
|   └── index.html
|   └── entry.js            // main app entry file
|   └── entry-client.js     // for client hydration
|   └── entry-server.js     // for server-side rendering
|   └── server.js
├── package.json

The entire dist folder is built by webpack including the index.html file which is just copied from the src folder. When I want to run in development environment, the index.html file requires /dist/app.js and /dist/vendor.js for client-side hydration to work. If they are not there, then only server-side rendered pages come through. The problem with this is that I cannot do any development work without rebuilding the dist folder entirely upon each file change which takes a long time and is a totally inefficient way to do development.

My webpack client build configuration does this:

const webpack = require('webpack');
const merge = require('webpack-merge');
const MiniCssExtractPlugin  = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const utils = require('./utils') // just resolves directories/files paths

const baseConfig = require('./webpack.base.config');
const isProduction = process.env.NODE_ENV === 'production';

module.exports = merge(baseConfig, {
    entry: {
        app: utils.resolve('/src/entry-client.js')
    },
    output: {
        path: utils.resolve('/dist'),
        filename: '[name].js',                   // generates app.js in ./dist folder
        sourceMapFilename: '[name].js.map', 
    },
    resolve: {
        extensions: ['.js', '.vue'],
    },
    optimization: {
        minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
        splitChunks: {
            cacheGroups: {
                commons: {
                    test: /[\\/]node_modules[\\/]/,
                    name: "vendor",                 // generates vendor.js file in ./dist
                    chunks: "all",
                },
            },
        },
    },
    module: {
        rules: [
            {
                test: /\.css?$/,
                use: [
                  {
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        sourceMap: !isProduction,
                        // only enable hot in development
                        hmr: !isProduction,
                        // if hmr does not work, this is a forceful method.
                        reloadAll: true,
                    },
                  },
                  'css-loader',
                ]
              },

    plugins:
        (isProduction ?
        [
            new MiniCssExtractPlugin({
                filename: 'assets/css/site.css', 
            }),
        ] : [
            new MiniCssExtractPlugin({
                filename: 'assets/css/site.css',
                hmr: true,
            }),
            new webpack.HotModuleReplacementPlugin(),
        ]
    )
});

The above only runs if I perform npm run build:client. My dev setup is like this:

/**
 * Setup webpack-dev-middleware and webpack-hot-middleware.
 * Rebuild SSR bundle on src files change.
 * 
 * @param {Object} app Express application 
 * @param {Function} onServerBundleReady Callback
 */
const setupDevServer = (app, onServerBundleReady) => {
    const webpack = require('webpack');
    const MFS = require('memory-fs');
    const path = require('path');
    const clientConfig = require('./webpack.client.config');
    const serverConfig = require('./webpack.ssr.config');

    // additional client entry for hot reload
    clientConfig.entry.app = ['webpack-hot-middleware/client', clientConfig.entry.app];

    const clientCompiler = webpack(clientConfig);

    // setup dev middleware
    app.use(require('webpack-dev-middleware')(clientCompiler, {
        publicPath: clientConfig.output.publicPath,
        serverSideRender: true,
        logLevel: 'silent'
    }));

    // setup hot middleware
    app.use(require('webpack-hot-middleware')(clientCompiler));

    // watch src files and rebuild SSR bundle
    global.console.log('Building SSR bundle...');
    const serverCompiler = webpack(serverConfig);
    const mfs = new MFS();

    serverCompiler.outputFileSystem = mfs;
    serverCompiler.watch({}, (error, stats) => {
        if (error) throw error;

        global.console.log(
            `${stats.toString({
                colors: true,
                modules: false,
                children: false,
                chunks: false,
                chunkModules: false,
            })}\n\n`
        );

        if (stats.hasErrors()) {
            console.error(stats.compilation.errors);
            throw new Error(stats.compilation.errors);
        }
        // read bundle generated by vue-ssr-webpack-plugin        
        const bundle = JSON.parse(
            mfs.readFileSync(path.join(clientConfig.output.path, 'vue-ssr-server-bundle.json'), 'utf-8')
        );
        onServerBundleReady(bundle);
    });
};

module.exports = setupDevServer;

The setup-dev-server.js configuration above never rebuilds the app.js or vendor.js files in the ./dist folder upon changes to any other files which means development mode is pointless. How do I solve this so that any change to a Home.vue for example triggers a recreation of app.js and vendor.js so my updates actually work?

volume one
  • 6,800
  • 13
  • 67
  • 146

0 Answers0