How to start moving legacy codebase to webpack
Tags: webpack, javascript
Published: 2021-09-11 10:18:24

Let's say we inherit a legacy JavaScript project, and it's our job to keep it alive. Often in those situations:

- you have an old and outdated codebase that is far from current best practices
- application works and is bringing money to the organization or solving some problem
- it's too big to even hope for rewriting everything from scratch

In this article, I'll show you how you can start migrating a codebase like this to webpack - so we can replace the old build infrastructure based on half-abandoned projects like Grunt; maybe achieve smaller files to be transferred to the user & keep ourselves up to date with the industry standards.

# Legacy codebase
To simplify, our legacy application is `index.html`:
```HTML
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>webpack</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link
      rel="stylesheet"
      href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"
    />
    <link rel="stylesheet" href="/resources/demos/style.css" />
    <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
    <script>
      $(function () {
        $("#datepicker").datepicker();
      });
    </script>
  </head>
  <body>
    <p>Date: <input type="text" id="datepicker" /></p>
  </body>
</html>
```
It is an input that uses jQuery UI Datepicker, and it looks like:
![legacy-app-start.png](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/er1ca3fbkcay3ih16mnr.png)

The way it's written has few features that JavaScript bundlers let us move away from:
* all the dependencies are specified in `index.html` - so we need to keep it up to date to our JS files
* we need to know what files are needed by our code, and all the 3rd party dependencies
* it's our job to load the files in the correct order. In my example, importing the files as:
```HTML
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>                 
    <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
```
would not work
* everything is imported to the global namespace - each dependency sees and can use (or mess up with) other dependencies

# Migration plan
We could most likely migrate my simple example to webpack in one go. For bigger projects, it's not an option - too many things are interconnected, and it could take a really long time to do it all. It's unlikely we would get approval for spending a week or two on code setup only. Another problem is that this big-bang approach offers very little feedback along the way. We could learn very late that one of the 3rd party libraries we've been using has some problems when built with wepback.

Let's take the smallest step possible to avoid those issues - add webpack, and move jQuery import there.

# Adding webpack
First, let's turn the folder we keep our `index.html` into npm package:
```sh
$ npm init -y
Wrote to /home/marcin/workspace/github/tmp/webpack-expose-loader/package.json:

{
  "name": "webpack-expose-loader",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
``` 

add webpack as a dependency
```shell
npm install --save-dev webpack webpack-cli
+ webpack-cli@4.8.0
+ webpack@5.52.1
updated 2 packages and audited 121 packages in 10.088s

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities
```

# Default configuration
To avoid creating `wepback.config.js`, we make sure we keep on using default locations for the source & output files. So we will have `src/index.js`:
```JS
console.log("Hello!");
```
and we add to `index.html`:
```HTML
    <script src="dist/main.js"></script>
```
Before all the other JS imports. Then, we add a build script to `package.json`:
```JSON
{
  ...
  "scripts": {
    ...
    "build": "webpack --mode=production"
...
```
and we can build with:
```shell
$ npm run build

> webpack-starter@1.0.0 build /home/marcin/workspace/github/webpack-expose-loader
> webpack --mode=production

asset main.js 22 bytes [compared for emit] [minimized] (name: main)
./src/index.js 23 bytes [built] [code generated]
webpack 5.52.1 compiled successfully in 163 ms
```
The application should work as before, but with a 'test' log in a console.

# Adding jQuery as a dependency

To start our migration, let's install jQuery in version 1.12.4 as a dependency:
```shell
$ npm install --save jquery@1.12.4
+ jquery@1.12.4
added 1 package from 1 contributor and audited 122 packages in 1.399s
```
Now, we can import jquery from our `src/index.js`:
```
import jQuery from "jquery";

console.log(jQuery);
```
When we rebuild:
```shell
npm run build                       

> webpack-starter@1.0.0 build /home/marcin/workspace/github/webpack-expose-loader
> webpack --mode=production

asset main.js 95.3 KiB [compared for emit] [minimized] (name: main) 1 related asset
runtime modules 663 bytes 3 modules
cacheable modules 287 KiB
  ./src/index.js 51 bytes [built] [code generated]
  ./node_modules/jquery/dist/jquery.js 287 KiB [built] [code generated]
webpack 5.52.1 compiled successfully in 2557 ms
```
We can see the output `main.js` is much bigger *95.3 KiB*, so we clearly include jQuery in our code. But if we remove from `index.html`:
```HTML
    <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
```

our date picker will be broken, and we will see in the console log:

![import-errors.png](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qiiy68xigp8j1mjn2dq.png)

That is because webpack is isolating modules. The imports are not polluting the global scope, and each module can access only things it explicitly imported. It's a good thing in the long term - it helps us avoid invisible coupling between modules, which can be very confusing. But in baby-step refactoring, we need to work around it.

# expose-loader
[expose-loader](https://webpack.js.org/loaders/expose-loader/) is a webpack loader that allows us to pollute a global scope with the import from a given file. To use it first, we need to install it as a dev dependency:
```shell
$ npm install --save-dev expose-loader
+ expose-loader@3.0.0
added 1 package from 1 contributor and audited 123 packages in 1.926s
```

Then we should change the import line in our `src/index.js`:
```JS
import jQuery from "expose-loader?exposes=$,jQuery!jquery";

console.log(jQuery);
```

The code means as follow:
* `import jQuery from` is a part of the import that makes it usable in the current file
* `"expose-loader!` is a special import syntax understood by wepback. It picks the loader to be used for the import that is specified after `!`
* `?exposes=$,jQuery` option provided to the loader. In this case, we want the default export of the file after `!` to be put on the global scope as `$` (for the script in `index.html`) and as `jQuery` - for the plugin
* `!jquery` what we are importing

With those changes in place, after building, the application should work as before.

# Links
* [the example repository](https://github.com/how-to-js/webpack-expose-loader)
* my [video course about webpack](https://bit.ly/webpack-course)

# Summary
We have walked through an example of how to start using a webpack in a legacy project. Let me know in the comment what experience with migration to webpack you have had so far.