Integrating Webpack 4 with a backend framework

Sofia Rocha
4 min readJul 2, 2018

--

Webpack 4 has been out for a couple of months, and numerous articles have been written on it. But here at Unbabel, most of our projects use some kind of Python framework, usually Flask or Django, and I didn’t find that much information on how making it work with those (non-Node) frameworks.

I wish I could tell you that it worked out of the box — zero confusion looks while reading old Github comments, scavenging for clues, and zero trial and error. But it wasn’t, I ended up spending a couple of hours setting this up. Here are some tips, so you don’t have to.

If you are just wondering “Does the team at Unbabel use some type of template or project boilerplate for new projects?”, just click here.

Why should you upgrade?

First things first, if your setup works for you (and your team) and you’re efficient using it, keep focused on development instead of tooling.

The main improvements of version 4 are performance, more sensible defaults and modes (which lets you run Webpack without a webpack.config.js file), and you can read more about it here.
We wanted to upgrade because we felt our configuration (which was sort of based on the vue.js Webpack config template) was too complicated and difficult to set up on new projects.

Now, we have one Webpack config file (instead of two folders and 10+ files) and have been using it on new projects, while we try to find the time to migrate the old ones.

File and Folder Structure

You can check out our webpack.config.js here. The main thing you should know while reading it is that our projects usually have a root folder that contains, at least:

[root]
|-- webpack.config.js
|-- package.json
|-- /node_modules
|-- /project
|-- /templates
|-- /static
| |-- /src
| |-- /dist
| |-- ...
|-- ...

The templates used in the views come from /project/templates/ and all the static assets, both the source and the production ones, come from /project/static/. This means that in our home.html template file, we link to our static files like this:

<script src="{{ url_for('static', filename='dist/js/home.js') }}"></script>

The devServer and Proxies

Here’s a simple configuration for devServer:

devServer: {
contentBase: path.join(__dirname, './project/'),
publicPath: '/static/dist/',
watchContentBase: true,
port: 9001,
},

But when running this dev server, we need the URLs, and views from Flask.
Webpack by itself doesn’t know https://localhost:9001/login/, but Flask does. So we need devServer to only serve some of the requests: the ones related to static assets like CSS and Javascript files. We can do this using the proxy option:

devServer: {
contentBase: path.join(__dirname, './project/'),
publicPath: '/static/dist/',
watchContentBase: true,
port: 9001,
proxy: {
'!(/static/dist/**/**.*)': {
target: 'http://127.0.0.1:5000',
},
},
},

'!(/static/dist/**/**.*)' is a regex expression that translates to "everything except files inside the /static/dist/ path”.

This means that the request:

  • https://localhost:9001/login/ goes to https://localhost:5000/login/
  • https://localhost:9001/static/dist/js/login.js is served by Webpack, that returns the in-memory, processed, login.js file

Copying and compressing images

We are using Vue.js components more and more, but there are still pages or sections that don’t need to be Javascript apps, and on those, we have image assets. Since those image assets are not used on a Javascript file that is processed by Webpack, they will be completely ignored.

But to keep things clean and uniform, we need them to go to the /dist/img/ folder:

plugins: [
new CopyWebpackPlugin([{
from: 'project/static/src/img',
to: '../dist/img',
}]),
],

This will copy all the image assets (or any file or folder, CopyWebpackPlugin is not picky), and if we want them to be minified, we add the ImageminPlugin plugin:

plugins: [
new CopyWebpackPlugin([{
from: 'project/static/src/img',
to: '../dist/img',
}]),
new ImageminPlugin({ test: /\.(jpe?g|png|gif|svg)$/i }),
],

Standalone .scss files and SASS in .vue components

We have two situations where we use SASS: on standalone files or inside .vue files, to style the components, and we need to compile both. We add a .scss file as an entry point, as usual:

entry: {
'css/basic': './project/static/src/scss/all.scss',
'js/login': './project/static/src/js/login.js',
},

The standalone file needs more loaders than the SASS on .vue files (mainly so the live-reloading works). So in the rule for .scss files, we add:

{
test: /\.scss$/,
oneOf: [
// config for scss inside .vue files
{
resourceQuery: /^\?vue/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader',
],
},
// config for stand-alone .scss files
{
use: [
'css-hot-loader',
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader',
],
},
],
},

The oneOf property is uncommon but useful in this case, as it will let us use a different set of loaders depending on the type of file.

Watching template files

We are now able to edit .vue and .scss files, and the devServer will automatically reload the page when the files are changed. However, the same doesn't happen when you change a .html file in the /templates/ folder.
We can change that by adding the template file to the entries:

entry: {
'html/home': './project/templates/home.html',
},

And make sure we have a loader in the Array of rules for this kind of files:

rules: [
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
},
],
},
],

The processed .html files will appear on the /dist/html/ folder, but since we won't be using them, we can delete them:

plugins: [
new WebpackShellPlugin({
onBuildEnd: ['rm -rf project/static/dist/html && echo "deleted the dist/html folder"'],
}),
],

TL;DR

The end result of making Webpack 4 work with a backend framework isn’t complicated at all but the journey might have some headaches.

You can check the webpack.config.js file to get started or take a look at our webpack-flask-starter, our starting point for new projects.

If you have a question or suggestion, or found a typo, let us know!

--

--