Blog

Migrating a Vue App to Vue-CLI

23 Jun 2020 - Von Christian Meyer

As with most software projects, when working on a Vue app for a couple of years, there comes a time when the framework developers have made breaking changes, but have also added interesting features that make updating the framework both desirable and painful. In our case, we were maintaining and improving a Vue app that was a couple of years old already and still using Webpack 3 as a build tool. Large and seasoned apps usually look back on a broad history of changes to the build pipeline and dependencies that make simply updating Webpack unfeasible. However, as Webpack 3 grew older and the community moved on, it became obvious that it was high time for us to get ourselves up to date. Looking at the available options, we found that Vue CLI offers a very clean new way to manage build pipelines and dependencies with less code involved. So I decided to take the plunge and migrate our app to Vue CLI cleaning up a lot of legacy code along the way.

General approach

Before writing any code, it’s best to first take stock of your dependencies and make note of which ones you still need and which ones can be dropped. For a newer build pipeline you might be able to replace some dependencies with new or different implementations. You should also have an idea about all the things your build pipeline is actually doing so that you can test for that when you’re done migrating. When compared to the Webpack 3 build, I found that the Vue CLI build process works very different in a lot of places. So when thinking about how to approach the whole migration I came to the conclusion that, instead of performing an update on the current project, it would be best to create a new Vue CLI project altogether and then perform a piece by piece migration of the various parts of the build process along with only some of the components. With each new piece added to the new project, I would then make sure that everything still runs they way it’s supposed to.

Starting a Vue CLI project

Setting up a Vue CLI project is explaned very well on their offical page, so I’ll just give you a cursory quickstart here. If you haven’t done so already, you need to first install vue-cli:

npm install -g @vue/cli

Then just create the project with a name of your choosing:

vue create my-project

This will start the project creation wizard asking you about which features to include such as typescript support, unit testing and more. And that’s it! You should be able to run your project now.

cd my-project
npm run serve

Webpack aliases

We’re using Webpack aliases to provide shorthands for frequently used directories. These can be configured in Webpack like this:

// webpack.conf.js
module.exports = {
  ...
  resolve: {
    alias: {
      "@": resolve("src"),
      ...
    }
  }
  ...
}

For build configuration Vue CLI uses a special file now called vue.config.js. It’s an additional layer on top of the Webpack configuration itself. You can read more about it in the official docs. The exported module is used by Vue CLI to produce a webpack config under /node_modules/@vue/cli-service/webpack.config.js. You can still make changes to the Webpack configuration using vue.config.js in a similar way via the key configureWebpack.

// vue.config.js
module.exports {
...
  configureWebpack: {			
    resolve: {
      alias: {
        "@": path.join(__dirname, "src"),
        ...
      }
    }
  }
...
}

And that’s all that’s needed to customize Webpack again.

A quick note on static assets and path shorthands: If you are referencing assets like background images using aliases via inline html attributes or css, you need to use the ~ keyword for referencing the path. So if you want to reference your logo in src/assets/logo.png and your alias is configured as shown above:

// resolving in inline html attribute
<div src="~@/assets/logo.png"></div>

// resolving in css
<div class="logo"></div>

<style>
  .logo {
    background-image: url("~@/assets/logo.png");
  }
</style>

Vue CLI Plugins

Vue CLI provides a plugin API that should be used whenever possible instead of npm when adding new dependencies. Many major modules already provide CLI plugins like the fantastic css framework Vuetify. You can install such plugins by simply calling vue add with the plugin name.

vue add vuetify

If the plugin can be found, it will be installed and can make changes to the vue.config.js. Some plugins may also guide you through their own setup wizard after installation. Afterwards, you can initialize and import your plugin in the main.ts as usual.

Webpack-Chain

Vue CLI makes use of webpack-chain internally and exposes it in vue.config.js. It’s another abstraction layer that’s convenient for modifying plugin and loader options. For different build flavours I needed a way to copy some static files from the flavour’s directory to dist. Vue CLI comes with a copy-plugin preinstalled. In the vue.config.js, I just needed to add a function called chainWebpack to the module with a single config parameter. To modify the copy-plugin, I could then access it by name and “tap” into its arguments.:

// vue.config.js
module.exports = {
...
  chainWebpack: config => {
  // copy flavour files to dist folder
    config.plugin("copy").tap(args => {
      args[0].push({
        from: "/flavour/path",
        to: "/dist",
        toType: "dir",
      });
      return args;
    });
  }
}

Loaders can be modified in much the same way. Let’s say you want to add a new loader for plain html files. First install the html-loader module.

npm i -D html-loader

Then simply add a new rule for html-files and specify the loader to use.

// vue.config.js
module.exports = {
...
  chainWebpack: config => {
    // loader for some plain html-files
    config.module
      .rule("my-html-rule")
      .test(/\.html$/)
      .include.add("path/to/html-files")
      .end()
      .use("html-loader")
      .loader("html-loader");
  }
}

Migrate Vue components piece by piece

Now you should be ready to migrate some Vue components. You may pick a topical chunk of components that run together, install their dependencies and see if they work as they should. It’s probably best to first get your Vue-Router ready if you’re using it. This step should be straightforward. If you don’t have that many components you might as well move them all at once, but doing it in a piece by piece fashion also helps you spot some legacy code and dependencies in your components that you don’t need anymore.

In this step, you might encounter a build warning that looks something like this:

warning

chunk offer_create~offer_detail [mini-css-extract-plugin]
Conflicting order between:
* css ./node_modules/css-loader??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??postcss!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/views/Offer/components/application-questions/application-questions.vue?vue&type=style&index=0&id=69b13b08&scoped=true&lang=css&
* css ./node_modules/css-loader??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??postcss!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/MutipleEvents.vue?vue&type=style&index=0&id=fe6da6ea&scoped=true&lang=css&
...

This happens if you have multiple components importing the same styles, but each does so in a different order. You need to determine if this actually causes problems for you, but chances are that it won’t if the App was working fine before migration. You can read more about this warning here. If you have safely determined that this does not cause problems for you, you may want to just ignore the warning in your vue.config.js like so:

// vue.config.js
module.exports = {
	...
	css:
		// css plugin should only be active in production environment
    process.env.NODE_ENV === "production"
      ? {
          extract: { ignoreOrder: true },
        }
      : undefined,
}

Environment Variables

Runtime Variables

If you’re using environment variables during your app’s runtime, you need to be aware that Vue CLI removes all environment variables after the build. This is done to prevent accidentally exposing secrets through those variables. For any variables that are relevant beyond the build time of your app, you can prevent this behaviour by prefixing it with VUE_APP_. So if you had a variable called API_URL before, you have to rename it to VUE_APP_API_URL.

Variable files

Vue CLI uses .env to read environment variables. Depending on your use case, you may want to move your variables there. You can also create .env.local files which are preconfigured to be ignored by Vue CLI’s generated .gitignore. To specify variables for a specific runtime environment, you can put the environment name as a file suffix. From the docs:

.env                # loaded in all cases
.env.local          # loaded in all cases, ignored by git
.env.[mode]         # only loaded in specified mode
.env.[mode].local   # only loaded in specified mode, ignored by git

You’re done!

That about covers all the most important aspects I had to deal with for migrating the Vue application. Of course, don’t forget to adjust your Readme file for you fellow developers so that they may easily understand how to properly handle all of those new aspects.

Good luck Migrating!


Picture credits:
Adobe Stock

Christian Meyer

Ich bin ein Software Engineer mit Leidenschaft für App-Entwicklung. Ich liebe es neuartige Technologien und Frameworks im Bereich Mobile-, Desktop- oder Web-Entwicklung auf die Probe zu stellen und meine Erfahrungen mit der Welt zu teilen.