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.
Source:stackexchange.com