[Vuejs]-Integration of back-end webpack with Vue-CLI 3

0👍

Ended up pretty much having a base webpack config and extending client and server webpack configuration.

My app root webpack.config.js looks like this:

const { config: client } = require('./src/client/webpack.config');
const { config: server } = require('./src/server/webpack.config');

/**
 * Webpack Build File
 *
 * This webpack configuration is used for only building the client and server
 * bundles. It imports both of these from their respective directories, but
 * allows for overrides if required.
 *
 * Other dev tools such as watching, hot module reloading etc. has been split
 * out into other config files
 *
 * @param {object} env Webpack `env` object
 */
module.exports = ({ mode = 'development' } = {}) => ([
  {
    ...client({ mode }, process.env),
  },
  {
    ...server({ mode }, process.env),
  },
]);

My src/client/webpack.config.js looks like this:

const { resolve: _resolve, sep } = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TSConfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackRootElementPlugin = require('html-webpack-root-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const { DefinePlugin } = require('webpack');

const paths = {
  src: {
    rootDir: _resolve(__dirname) + sep,
    app: _resolve(__dirname, 'app') + sep,
  },
  dist: {
    rootDir: _resolve(__dirname, '..', '..', 'dist'),
    app: _resolve(__dirname, '..', '..', 'dist', 'public'),
  },
};

/**
 * Client webpack build configuration.
 *
 * This webpack config produces a bundle for the client-side application only.
 *
 * @param {object} webpackEnv Webpack env object (basically any/all options passed in via the CLI)
 * @param {object} processEnv Process env object (environment variables from process.env)
 */
const config = ({ mode = 'none' }, { APP_NAME = '', BASE_URL = '/' } = {}) => ({
  name: 'client',
  target: 'web',
  mode,
  entry: {
    app: paths.src.app + 'main.ts',
  },
  output: {
    path: paths.dist.app,
  },
  optimization: {
    runtimeChunk: 'single',
  },
  resolve: {
    extensions: ['.ts', '.js', '.vue'],
    plugins: [
      new TSConfigPathsPlugin({
        configFile: paths.src.rootDir + 'tsconfig.json',
      }),
    ],
  },
  context: paths.src.rootDir,
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        include: paths.src.rootDir,
        exclude: /node_modules/,
        use: [
          {
            loader: 'ts-loader',
            options: {
              happyPackMode: true,
            },
          },
        ],
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
      {
        test: /\.s?[ac]ss$/,
        use: [
          'style-loader',
          'css-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.(png|jpe?g|gif|svg|eot|ttf|woff|woff2)$/i,
        loader: 'file-loader',
        options: {
          name: 'assets/[name].[hash].[ext]',
          esModule: false,
        },
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      favicon: paths.src.app + 'assets/logo.png',
      title: APP_NAME,
    }),
    new HtmlWebpackRootElementPlugin('app'),
    new VueLoaderPlugin(),
    new DefinePlugin({
      'process.env.BASE_URL': JSON.stringify(BASE_URL),
    }),
  ],
});

module.exports = {
  config,
  paths,
};

and my src/server/webpack.config.js looks like this:

const { resolve: _resolve, sep } = require('path');
const TSConfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const nodeExternals = require('webpack-node-externals');
const { IgnorePlugin } = require('webpack');

const paths = {
  src: _resolve(__dirname) + sep,
  dist: _resolve(__dirname, '..', '..', 'dist'),
};

/**
 * NestJs uses a custom wrapper around require() that allows it to show a
 * warning when some extra package needs to be installed. This causes problems
 * with webpack, so we're blacklisting packages we're not using with the
 * IgnorePlugin below.
 *
 * To de-blacklist a package, just remove it from this array.
 */
const nestBlacklist = [
  '^cache-manager$',
  '^@nestjs/microservices$',
  // packages below are required from microservices
  '^amqp-connection-manager$',
  '^amqplib$',
  '^grpc$',
  '^mqtt$',
  '^nats$',
  '^redis$',
];

/**
 * Server webpack build configuration.
 *
 * This webpack config produces a bundle for the server-side application only.
 *
 * @param {object} webpackEnv Webpack env object (basically any/all options passed in via the CLI)
 * @param {object} processEnv Process env object (environment variables from process.env)
 */
const config = ({ mode = 'none' }) => ({
  name: 'server',
  mode,
  target: 'node',
  entry: paths.src + 'main.ts',
  externals: [nodeExternals()],
  output: {
    path: paths.dist,
    filename: 'server.js',
  },
  resolve: {
    extensions: ['.ts', '.js'],
    plugins: [
      new TSConfigPathsPlugin({
        configFile: './tsconfig.build.json',
      }),
    ],
  },
  context: paths.src,
  optimization: {
    minimize: false,
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
      {
        test: /\.ts$/,
        include: paths.src,
        exclude: /node_modules/,
        use: [
          {
            loader: 'ts-loader',
            options: {
              happyPackMode: true,
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new IgnorePlugin({
      contextRegExp: /@nestjs/,
      resourceRegExp: new RegExp(nestBlacklist.join('|')),
    }),
  ],
  node: {
    __dirname: false,
  },
});

module.exports = {
  config,
  paths,
};

This allows me to have distinct server and client side compilation configuration, but in such a way that’s re-usable in other configurations. So, for webpack dev server – I have webpack.dev.config.js which contains the following:

const client = require('./src/client/webpack.config');
const server = require('./src/server/webpack.config');
const { HotModuleReplacementPlugin } = require('webpack');

const {
  SERVER_PORT = 3000,
} = process.env;

/**
 * Watch settings are the same between client and server, so we're keeping them
 * here for consistency
 */
const watchConfig = {
  watch: true,
  watchOptions: {
    ignored: /node_modules/,
  },
};

/**
* Development Webpack Build File
*
* This webpack configuration extends `webpack.config.js` and builds on it to
* provide hot module replacement, watch moide and a dev server for the
* client-side code
*
* Other dev tools such as watching, hot module reloading etc. has been split
* out into other config files
*
* @param {object} env Webpack `env` object
*/
module.exports = ({ mode = 'development' } = {}) => ([
  {
    ...client.config({ mode }, process.env),
    ...watchConfig,
    devServer: {
      contentBase: client.paths.dist.app,
      historyApiFallback: {
        rewrites: [
          { from: /./, to: '/index.html' },
        ],
      },
      port: 8000,
      host: '0.0.0.0',
      hot: true,
      hotOnly: true,
      proxy: {
        '/api': `http://localhost:${SERVER_PORT}/`,
      },
    },
  },
  {
    ...server.config({ mode }, process.env),
    ...watchConfig,
    plugins: [
      ...server.config({ mode }, process.env).plugins,
      new HotModuleReplacementPlugin(),
    ],
  },
]);

This config provides a setup that produces a server.js which then serves up a directory (in this case ‘/public’`) as it’s static files dir.

Leave a comment