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?