Webpack Javascript Bundling for Both Front-end and Back-end (nodejs)

Soon Hin Khor, Ph.D.
Code Oil
Published in
6 min readApr 1, 2018

The original purpose, and it remains the most common use case for Webpack, is front-end javascript bundling. This involves bundling a piece of javascript code, and all its dependencies into a single javascript file that can be served as a static javascript and referenced from a HTML page.

If you are unfamiliar with front-end javascript bundling, the next section serves as a crash course introduction else skip straight to the next section ‘Webpack Front-end and Back-end Javascript Bundling’.

This article is about using Webpack to not only bundle your front-end javascript, but the back-end javascript as well, in the case where you are using a javascript-based backend such as nodejs.

One good reason for using Webpack for your nodejs back-end is that you are already using Webpack for your front-end, and you want to streamline your build tools.

The motive for writing this article is two-fold:

  • Explain the differences in using Webpack for back-end vs. for front-end javascript bundling
  • Provide step-by-step instruction to bundle javascript for both back-end, and front-end

Webpack Front-end Javascript Bundling Crash Course

This introduction is based on the example on the official Webpack site.

  1. You write a function, etc., inside a module, packaged in a file (bar.js)
// bar.js
export default function bar() {
// Code for bar function
...
}

2. You import the module and use it with other libraries, e.g., React (front-end framework) in your front-end javascript code (front.js)

// front.js
import bar from '/.bar';
import React from 'react';
export default class MyButton extends React.Component {
// Code that use bar function and React library
...
}

3. You use webpack to create a bundle, i.e., a single javascript (‘bundle-front.js’) containing all dependencies (bar module, React library, etc.) with ‘front.js’ as the starting point.

// webpack.config.js
module.exports = {
entry = './front.js',
output: {
filename: 'bundle-front.js'
}
}

4. Run the javascript code in ‘bundle-front.js’ file on your web page

// index.html
<!doctype html>
<html>
<head>
...
</head>
<body>
...
<script src="bundle-front.js"></script>
</body>
</html>

Front-end vs. Back-end Javascript Bundling

Front-end javascript bundling is used in any project regardless of their back-end stack, which can be ruby, python, etc. The front-end javascript bundle output is merely served from the back-end stack as a static javascript file.

The confusion starts when the back-end stack is also javascript-based, e.g., nodejs. Are you then trying to bundle the javascript for front-end, back-end or both when you use Webpack?

You have 2 choices:

  1. Use Webpack for bundling front-end javascript code as described, and Gulp or Grunt for building your back-end javascript code. The disadvantage is there is a lot of effort and setup redundancy in managing both build systems.
  2. Use Webpack for bundling both front-end and back-end javascript code. Most people are not aware that you need to bundle the front-end and back-end javascript code ‘separately’, creating 2 output bundles

We will assume that you want to use Webpack in case #2, and there are 2 ways to do this:

A. Distinct Webpack configuration files for front-end and back-end

B. Unified Webpack configuration file for both front-end and back-end

A. Distinct Configuration Files For Front-end and Back-end

The simplest way is to create 2 Webpack configurations

  • webpack-back.config.js
  • webpack-front.config.js

You run Webpack command twice, each time using a different configuration file to generate 2 output bundles, e.g., bundle-front.js, and bundle-back.js.

// Generate bundle-front.js
webpack --config ./webpack-front.config.js
// Generate bundle-back.js
webpack --config ./webpack-back.config.js

You start your backend server with ‘bundle-back.js’ using:

node bundle-back.js

And on your main html page you run ‘bundle-front.js’ as follows:

// index.html
<!doctype html>
<html>
<head>
...
</head>
<body>
...
<script src="bundle-front.js"></script>
</body>
</html>

The differences in the Webpack configurations are the following parameters:

  • target
  • entry
  • output
  • externals
  • devServer
  • devtool

webpack-back.config.js

const path = require('path');
const nodeExternals = require('webpack-node-externals');
module.exports = {
target: "node",
entry: {
app: ["./back.js"]
},
output: {
path: path.resolve(__dirname, "../build"),
filename: "bundle-back.js"
},
externals: [nodeExternals()],
};

webpack-front.config.js

const path = require('path');module.exports = {
target: "web",
entry: {
app: ["./front.js"]
},
output: {
path: path.resolve(__dirname, "../build"),
filename: "bundle-front.js",
},
devServer: {
host: '0.0.0.0', // Required for docker
publicPath: '/assets/',
contentBase: path.resolve(__dirname, "./views"),
watchContentBase: true,
compress: true,
port: 9001
},
devtool: 'inline-source-map',
}

‘target’:

  • For back-end, this is ‘node’, for front-end, this is ‘web’

‘entry’:

  • The entry points (source files) for the back-end and front-end javascript points to different source javascript files, i.e., ‘back.js’, and ‘front.js’ respectively

‘output’:

  • The generated javascript bundle file for back-end and front-end are different files, i.e., ‘bundle-back.js’, and ‘bundle-front.js’ respectively

‘externals’:

  • The ‘webpack-back.config.js’ imports the nodeExternals method from ‘webpack-node-externals’ library
  • This method returns the list of dependency libraries in ‘./node_modules’ directory and putting the list into ‘externals’ exclude them from being bundled
  • For back-end, all the dependency libraries is installed into ‘./node_modules’ with yarn install or npm install during build time thus there is no need to include them in the back-end bundle
  • The front-end, on the other needs all dependencies to be bundled because the bundled javascript needs to be a stand-alone unit as it will be loaded and run on the user’s browser

‘devServer’

  • Only for front-end bundling. This sets up the webpack-dev-server, which eliminates the need to setup a webserver to serve the front-end bundle as a static javascript file during development. Moreover, it can automatically re-bundles the front-end bundle if it detects changes in the source code, to increase productivity; you do not have to restart your server, and reload your browser.

‘devtool’

  • Indicates the details in the sourcemap, which is used by the browser to tie the front-end javascript bundle execution to the original front-end javascript source code, which is useful for debugging
  • Only for front-end since back-end javascript has their own debugging tools

B. Unified Configuration Files For Front-end and Back-end

It is possible to combine webpack-back.config.js and webpack-front.config.js into a single webpack.config.js, which requires 3 steps:

  1. Combine all the ‘require’ statements and put them at the top
  2. Shove the ‘webpack-front.config.js’, and ‘webpack-back.config.js’ into two separate consts
  3. Combine the ‘module.exports’ and put them at the bottom to export the consts holding the webpack configurations
// Combined 'require' statements
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const webpack = require('webpack');
const frontConfig = {
// Stuff the entire webpack-front.config.js
// without the require and module.exports lines
...
}
const backConfig = {
// Stuff the entire webpack-back.config.js
// without the require and module.exports lines
...
}
// Combined 'module.exports'
module.exports = [ frontConfig, backConfig ];

This enables us to generate the back-end, and front-end bundles with a single Webpack command.

// Generate both bundle-front.js and bundle-back.js 
webpack --config webpack.config.js

However, I have encountered issues where the webpack-dev-server cannot start properly depending on the order of clientConfig, and serverConfig.

For details, see Webpack ‘targets’ concept page. react-starter-kit use a single webpack.config.js file as well. Neither examples however has ‘devServer’ configured.

Common Error Messages

The most telltale sign that you are not bundling front-end and back-end bundle correctly is when you see messages similar to :

Can't resolve 'fs' webpack

You are trying to use a node-based module in the front-end bundling.

The easiest way to solve this is to specify the ‘target’ in the Webpack configuration for your front-end ‘webpack-front.config.js’, as pointed out above.

// webpack-front.config.js
module.exports = {
target: "web",
...
}

If the case that problem persists, add the following to the Webpack configuration

// webpack-front.config.js
module.exports = {
node: {
fs: 'empty'
},
...
}

--

--

Soon Hin Khor, Ph.D.
Code Oil

Use tech to make the world more caring, and responsible. Nat. Univ. Singapore, Carnegie Mellon Univ, Univ. of Tokyo. IBM, 500Startups & Y-Combinator companies