Webpack Javascript Bundling for Both Front-end and Back-end (nodejs)
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.
- 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:
- 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.
- 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
ornpm 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:
- Combine all the ‘require’ statements and put them at the top
- Shove the ‘webpack-front.config.js’, and ‘webpack-back.config.js’ into two separate
const
s - Combine the ‘module.exports’ and put them at the bottom to export the
const
s 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'
},
...
}