Multiple webpack builds with shared vendors
Tags: webpack, aspnetcore, vendors
Published: 2019-11-29 13:06:08

**This blog post aims to teach you (and remind me) how to setup multiple client application build procedures with shared vendors. We will be using Asp.Net Core 2 and Webpack (with DllPlugin) to build our client-side code.**


Recently, we had to set up a build procedure for multiple client applications within a [.NET Core MVC](https://docs.microsoft.com/en-us/aspnet/core/mvc/overview?view=aspnetcore-2.2) app. A mix of MVC and [React](https://reactjs.org/) was used to display a user interface and [MobX](https://mobx.js.org) for client-side state management. To build client-side code, we used [Webpack](https://webpack.js.org/).

## Environment

Because we were using Webpack to build our code, we went on and used it to develop all of it, not just parts based on React and MobX. The requirement stated that the React part of the application has to be decoupled, and the core code (all of the UI logic and state) has to be built as a library. We didn't want React application to end up in that library; thus we used separate _webpack configs_

## Separate webpack configs - success!

We created a common config file that was mainly used for vendor optimizations. I won't go into the _optimization_ and caching strategies, but I encourage you to read this [blog post](https://medium.com/hackernoon/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758).

Also, we created two specialized _webpack configs_ used to build core code and React code.

```js
// webpack.config.app.js
//...
entry: {
    app: './client/js/app.js'
},
devtool: 'inline-source-map',
output: {
    filename: '[name].js',
    path: path.resolve(__dirname, './wwwroot/dist'),
    library: 'app',
    libraryTarget: 'umd',
    umdNamedDefine: true
}
//...
```

and

```js
// webpack.config.react.js
//...
entry: {
    'react-app': './client/ui/index.js'
},
output: {
    filename: '[name].js',
    path: path.resolve(__dirname, './wwwroot/dist')
}
//...
```

and a command to build it all (in package.json):
```json
{
    "scripts": {
        "build": "webpack --mode <development/production> --config webpack.config.app.js --config webpack.config.react.js"
    }
}
```

That was super easy - `npm run build` and we are almost over! We just had to include scripts in the templates, and that was it!

```html
<script asp-src-include="~/dist/modules/*.js"></script>
<script asp-src-include="~/dist/*.js"></script>
```

### We have a problem...

Now our library `app` is undefined, the React application is not displaying!

We can see that two builds occurred, and that is fine, because of the two _webpack configs_. But, what we can also see is that multiple modules were built twice. I assumed that these modules differ and that one overwrites another. Let us test that out. We can split output files in each config and compare the output with tools like [KDiff3](http://kdiff3.sourceforge.net/). 

We compared `vendor.babel.js` files in `/dist/ui` and `/dist/js` folders.

![Comparison](https://thepracticaldev.s3.amazonaws.com/i/e611wbo0n07xmq5z1ajc.png)

We can see that the files are not identical, meaning that one vendor file might not have everything needed for the other. This is precisely an issue in our case. So what are the possible solutions?

1. Split build to two different output folders and load that (probably the worst solution).
2. Use hash in output (we still end up with duplicate vendors).
2. Use shared vendors.

## Solution - shared vendors

We will use [DllPlugin](https://webpack.js.org/plugins/dll-plugin/). If you are coming from Microsoft stack, you will be (hopefully) familiar with the term DLL (Dynamic Link library).

If not, in short, DLL is a library that can be used by multiple programs simultaneously. For more information, read [this](https://support.microsoft.com/en-gb/help/815065/what-is-a-dll).

Let us get back to Webpack DllPlugin. Essentially it is the same as described above. The plugin is splitted into two parts: _DllPlugin_ and _DllReferencePlugin_. 

> Webpack DllPlugin:
> This plugin is used in a separate webpack config exclusively to create a dll-only-bundle. It creates a manifest.json file, which is used by the DllReferencePlugin to map dependencies.

What we have to do?
1. Create new webpack.config.vendors.js with DllPlugin.
2. Define our dependencies.
3. Add DllReferencePlugin to our _webpack.config.app.js_ & _webpack.config.react.js_.
4. Add build script for vendors.
5. Don't forget to include scripts.

We create `webpack.config.vendors.js` file just like any other Webpack config, but the entry point will point to our dependencies. Our dependencies are _react_, _react-dom_, _mobx_ and _mobx-react_. If we do that, we will end up with something like this:


```js
const webpack = require('webpack');
const path = require('path');

module.exports = {
    entry: [
        'react',
        'react-dom',
        'mobx',
        'mobx-react'
    ],
    output: {
        filename: 'vendors.[hash].js',
        path: path.resolve(__dirname, './wwwroot/dist/vendors'),
        library: 'vendor_lib_[hash]'
    },
    plugins: [
        new webpack.DllPlugin({
            name: 'vendor_lib_[hash]',
            path: path.resolve(__dirname, './wwwroot/dist/vendors/vendor-manifest.json')
        })
    ]
};
```

Now we can proceed to add _DllReferencePlugin_ into the `webpack.config.common.js`. We also have to reference the manifest file.

```js
//...
plugins: [
    //...
    new webpack.DllReferencePlugin({
        manifest: path.resolve(__dirname, './wwwroot/dist/vendor-manifest.json')
    })
]
//...
```

We create a separate build script, especially for vendors, and run it before the main build. The existence of `vendor-manifest.json` is **required** for running two other builds. 

Running vendors build script generates `dist/vendors` folder with all the vendors and the manifest. When we build the rest of the code, we end up with this:

![Dist folder](https://thepracticaldev.s3.amazonaws.com/i/5c040j445l68g9vwse1u.png)

> Notice the scripts with `~`. These are shared scripts between vendors and our code (it may be different in your case). This was the side effect of the _optimization_ in the `webpack.config.common.js`.

We have to include our scripts in the correct order and test our app.

The correct order would be: 

1. vendors
2. shared scripts
3. specific scripts

We have this in the `_Layout.cshtml` at the bottom of the _body_:

```html
    <script asp-src-include="~/dist/vendors/vendors.*.js"></script>
    <script asp-src-include="~/dist/vendors~**.js"></script>
    <script asp-src-include="~/dist/app.**.js"></script>
    <script asp-src-include="~/dist/react-app.**.js"></script>
```

And we are finally done!

## Conclusion
DllPlugin allows you to separate your app and vendors. Your primary focus stays on the app you are developing, and you won't have to wait for vendors to build. You build them once, and you generally don't worry about them anymore.

While there are many blog posts around this topic, I couldn't find a single one targeting ASP.NET Core 2. Entire code used for this blog post can be found here: https://github.com/Nodios/SharedVendorsSampleApp

### Space for improvements and known issues
If we try to add vendor optimization to the `webpack.config.vendors.js` the build messes up `vendor-manifest.json`. I am yet to find a reason why that happens. If you have an answer, please contact me. 

As you can see, incorporating scripts into the views is a tedious job. You have to be very careful with the ord
er of the scripts; otherwise, your code won't work. I used [HtmlWebpackPlugin](https://github.com/jantimon/html-webpack-plugin) to inject scrips in the view, but the problem is when you have multiple layouts and areas things tend to go out of hand - especially with shared scripts. If you have a solution to this, feel free to contact me as well.

---

This article is originally published on [Mono](https://mono.software/2019/11/15/multiple-webpack-builds-with-shared-vendors/).